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,551 @@
|
|
|
1
|
+
"""Discovery CLI commands for codebase scanning and graph integration.
|
|
2
|
+
|
|
3
|
+
This module provides commands to scan codebases, extract structural
|
|
4
|
+
information, and integrate discovered components into the unified context graph.
|
|
5
|
+
|
|
6
|
+
Supports Python, TypeScript, JavaScript, PHP, Svelte, and C#.
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
$ raise discover scan src/
|
|
10
|
+
$ raise discover scan . --language typescript --output json
|
|
11
|
+
$ raise discover build --input work/discovery/components-validated.json
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Annotated, Any
|
|
19
|
+
|
|
20
|
+
import typer
|
|
21
|
+
from rich.console import Console
|
|
22
|
+
|
|
23
|
+
from raise_cli.cli.error_handler import cli_error
|
|
24
|
+
from raise_cli.discovery.scanner import Language, ScanResult, scan_directory
|
|
25
|
+
from raise_cli.hooks.emitter import create_emitter
|
|
26
|
+
from raise_cli.hooks.events import DiscoverScanEvent
|
|
27
|
+
from raise_cli.output.formatters.discover import (
|
|
28
|
+
format_analyze_result,
|
|
29
|
+
format_build_result,
|
|
30
|
+
format_drift_result,
|
|
31
|
+
format_scan_result,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
discover_app = typer.Typer(
|
|
35
|
+
name="discover",
|
|
36
|
+
help="Codebase discovery and analysis commands",
|
|
37
|
+
no_args_is_help=True,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
console = Console()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@discover_app.command("scan")
|
|
44
|
+
def scan_command(
|
|
45
|
+
path: Annotated[
|
|
46
|
+
Path,
|
|
47
|
+
typer.Argument(
|
|
48
|
+
help="Directory to scan for source files",
|
|
49
|
+
exists=True,
|
|
50
|
+
file_okay=False,
|
|
51
|
+
dir_okay=True,
|
|
52
|
+
resolve_path=True,
|
|
53
|
+
),
|
|
54
|
+
] = Path("."),
|
|
55
|
+
language: Annotated[
|
|
56
|
+
str | None,
|
|
57
|
+
typer.Option(
|
|
58
|
+
"--language",
|
|
59
|
+
"-l",
|
|
60
|
+
help="Language to scan: python, typescript, javascript, php, svelte, csharp (auto-detect if not set)",
|
|
61
|
+
),
|
|
62
|
+
] = None,
|
|
63
|
+
output: Annotated[
|
|
64
|
+
str,
|
|
65
|
+
typer.Option(
|
|
66
|
+
"--output",
|
|
67
|
+
"-o",
|
|
68
|
+
help="Output format: human, json, or summary",
|
|
69
|
+
),
|
|
70
|
+
] = "human",
|
|
71
|
+
pattern: Annotated[
|
|
72
|
+
str | None,
|
|
73
|
+
typer.Option(
|
|
74
|
+
"--pattern",
|
|
75
|
+
"-p",
|
|
76
|
+
help="Glob pattern for files (default: language-specific)",
|
|
77
|
+
),
|
|
78
|
+
] = None,
|
|
79
|
+
exclude: Annotated[
|
|
80
|
+
list[str] | None,
|
|
81
|
+
typer.Option(
|
|
82
|
+
"--exclude",
|
|
83
|
+
"-e",
|
|
84
|
+
help="Patterns to exclude (can be repeated)",
|
|
85
|
+
),
|
|
86
|
+
] = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Scan a directory and extract code symbols.
|
|
89
|
+
|
|
90
|
+
Extracts classes, functions, methods, interfaces, and module docstrings
|
|
91
|
+
from source files. Supports Python, TypeScript, JavaScript, PHP, Svelte, and C#.
|
|
92
|
+
|
|
93
|
+
Output can be human-readable table, JSON, or summary statistics.
|
|
94
|
+
|
|
95
|
+
Examples:
|
|
96
|
+
# Scan current directory (auto-detect languages)
|
|
97
|
+
raise discover scan
|
|
98
|
+
|
|
99
|
+
# Scan Python files only
|
|
100
|
+
raise discover scan src/ --language python
|
|
101
|
+
|
|
102
|
+
# Scan TypeScript project
|
|
103
|
+
raise discover scan ./app --language typescript --output json
|
|
104
|
+
|
|
105
|
+
# Auto-detect but exclude tests
|
|
106
|
+
raise discover scan . --exclude "**/test_*" --exclude "**/__tests__/**"
|
|
107
|
+
"""
|
|
108
|
+
# Validate language if provided
|
|
109
|
+
lang: Language | None = None
|
|
110
|
+
if language:
|
|
111
|
+
if language not in (
|
|
112
|
+
"python",
|
|
113
|
+
"typescript",
|
|
114
|
+
"javascript",
|
|
115
|
+
"php",
|
|
116
|
+
"svelte",
|
|
117
|
+
"csharp",
|
|
118
|
+
):
|
|
119
|
+
cli_error(
|
|
120
|
+
f"Unsupported language: {language}",
|
|
121
|
+
hint="Supported: python, typescript, javascript, php, svelte, csharp",
|
|
122
|
+
exit_code=7,
|
|
123
|
+
)
|
|
124
|
+
lang = language # type: ignore[assignment]
|
|
125
|
+
|
|
126
|
+
# Pass user excludes or None to use DEFAULT_EXCLUDE_PATTERNS from scanner
|
|
127
|
+
exclude_patterns = exclude if exclude else None
|
|
128
|
+
|
|
129
|
+
result = scan_directory(
|
|
130
|
+
path,
|
|
131
|
+
language=lang,
|
|
132
|
+
pattern=pattern,
|
|
133
|
+
exclude_patterns=exclude_patterns,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Emit discover:scan event
|
|
137
|
+
emitter = create_emitter()
|
|
138
|
+
emitter.emit(
|
|
139
|
+
DiscoverScanEvent(
|
|
140
|
+
project_path=path,
|
|
141
|
+
language=lang or "auto",
|
|
142
|
+
component_count=len(result.symbols),
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
format_scan_result(result, path, output, language=lang)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@discover_app.command("analyze")
|
|
150
|
+
def analyze_command(
|
|
151
|
+
input_file: Annotated[
|
|
152
|
+
Path | None,
|
|
153
|
+
typer.Option(
|
|
154
|
+
"--input",
|
|
155
|
+
"-i",
|
|
156
|
+
help="Path to scan result JSON (reads stdin if not provided)",
|
|
157
|
+
),
|
|
158
|
+
] = None,
|
|
159
|
+
output: Annotated[
|
|
160
|
+
str,
|
|
161
|
+
typer.Option(
|
|
162
|
+
"--output",
|
|
163
|
+
"-o",
|
|
164
|
+
help="Output format: human, json, or summary",
|
|
165
|
+
),
|
|
166
|
+
] = "human",
|
|
167
|
+
category_map_file: Annotated[
|
|
168
|
+
Path | None,
|
|
169
|
+
typer.Option(
|
|
170
|
+
"--category-map",
|
|
171
|
+
"-c",
|
|
172
|
+
help="YAML file with custom path-to-category mappings",
|
|
173
|
+
),
|
|
174
|
+
] = None,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""Analyze scan results with confidence scoring and module grouping.
|
|
177
|
+
|
|
178
|
+
Takes raw scan output (from `raise discover scan --output json`) and
|
|
179
|
+
produces an analysis with confidence scores, auto-categorization,
|
|
180
|
+
hierarchical folding, and module grouping for parallel AI synthesis.
|
|
181
|
+
|
|
182
|
+
All analysis is deterministic — no AI inference required.
|
|
183
|
+
|
|
184
|
+
Examples:
|
|
185
|
+
# Analyze from file
|
|
186
|
+
raise discover analyze --input scan-result.json
|
|
187
|
+
|
|
188
|
+
# Pipe from scan
|
|
189
|
+
raise discover scan src/ -l python -o json | raise discover analyze
|
|
190
|
+
|
|
191
|
+
# JSON output
|
|
192
|
+
raise discover analyze --input scan-result.json --output json
|
|
193
|
+
|
|
194
|
+
# Summary only
|
|
195
|
+
raise discover analyze --input scan-result.json --output summary
|
|
196
|
+
"""
|
|
197
|
+
import sys
|
|
198
|
+
|
|
199
|
+
from raise_cli.discovery.analyzer import analyze
|
|
200
|
+
|
|
201
|
+
# Load scan result JSON
|
|
202
|
+
scan_json: str = ""
|
|
203
|
+
if input_file:
|
|
204
|
+
if not input_file.exists():
|
|
205
|
+
cli_error(
|
|
206
|
+
f"Input file not found: {input_file}",
|
|
207
|
+
hint="Run 'raise discover scan --output json' first",
|
|
208
|
+
exit_code=4,
|
|
209
|
+
)
|
|
210
|
+
scan_json = input_file.read_text(encoding="utf-8")
|
|
211
|
+
else:
|
|
212
|
+
# Read from stdin
|
|
213
|
+
if sys.stdin.isatty():
|
|
214
|
+
cli_error(
|
|
215
|
+
"No input provided",
|
|
216
|
+
hint="Pipe from scan: raise discover scan -o json | raise discover analyze\n"
|
|
217
|
+
"Or use --input: raise discover analyze --input scan-result.json",
|
|
218
|
+
exit_code=7,
|
|
219
|
+
)
|
|
220
|
+
scan_json = sys.stdin.read()
|
|
221
|
+
|
|
222
|
+
# Parse scan result
|
|
223
|
+
scan_result = ScanResult(symbols=[], files_scanned=0, errors=[])
|
|
224
|
+
try:
|
|
225
|
+
scan_data: dict[str, Any] = json.loads(scan_json)
|
|
226
|
+
scan_result = ScanResult(
|
|
227
|
+
symbols=[],
|
|
228
|
+
files_scanned=scan_data.get("files_scanned", 0),
|
|
229
|
+
errors=scan_data.get("errors", []),
|
|
230
|
+
)
|
|
231
|
+
# Parse symbols from JSON
|
|
232
|
+
from raise_cli.discovery.scanner import Symbol
|
|
233
|
+
|
|
234
|
+
for sym_data in scan_data.get("symbols", []):
|
|
235
|
+
scan_result.symbols.append(Symbol.model_validate(sym_data))
|
|
236
|
+
except (json.JSONDecodeError, KeyError, ValueError) as e:
|
|
237
|
+
cli_error(
|
|
238
|
+
f"Invalid scan result JSON: {e}",
|
|
239
|
+
hint="Input must be JSON from 'raise discover scan --output json'",
|
|
240
|
+
exit_code=7,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Load custom category map if provided
|
|
244
|
+
category_map: dict[str, str] | None = None
|
|
245
|
+
if category_map_file:
|
|
246
|
+
if not category_map_file.exists():
|
|
247
|
+
cli_error(
|
|
248
|
+
f"Category map file not found: {category_map_file}",
|
|
249
|
+
exit_code=4,
|
|
250
|
+
)
|
|
251
|
+
try:
|
|
252
|
+
import yaml
|
|
253
|
+
|
|
254
|
+
category_map = yaml.safe_load(category_map_file.read_text(encoding="utf-8"))
|
|
255
|
+
except ImportError:
|
|
256
|
+
cli_error(
|
|
257
|
+
"PyYAML required for --category-map",
|
|
258
|
+
hint="Install with: pip install pyyaml",
|
|
259
|
+
exit_code=6,
|
|
260
|
+
)
|
|
261
|
+
except Exception as e:
|
|
262
|
+
cli_error(f"Error reading category map: {e}", exit_code=7)
|
|
263
|
+
|
|
264
|
+
# Run analysis
|
|
265
|
+
result = analyze(scan_result, category_map=category_map)
|
|
266
|
+
|
|
267
|
+
# Save analysis.json
|
|
268
|
+
output_dir = Path("work/discovery")
|
|
269
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
270
|
+
output_path = output_dir / "analysis.json"
|
|
271
|
+
output_path.write_text(
|
|
272
|
+
json.dumps(result.model_dump(), indent=2, default=str),
|
|
273
|
+
encoding="utf-8",
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# Format and print
|
|
277
|
+
format_analyze_result(result, output)
|
|
278
|
+
|
|
279
|
+
if output != "json":
|
|
280
|
+
console.print(f"\n[dim]Saved: {output_path}[/dim]")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@discover_app.command("build")
|
|
284
|
+
def build_command(
|
|
285
|
+
input_file: Annotated[
|
|
286
|
+
Path | None,
|
|
287
|
+
typer.Option(
|
|
288
|
+
"--input",
|
|
289
|
+
"-i",
|
|
290
|
+
help="Path to validated components JSON (default: work/discovery/components-validated.json)",
|
|
291
|
+
),
|
|
292
|
+
] = None,
|
|
293
|
+
project_root: Annotated[
|
|
294
|
+
Path,
|
|
295
|
+
typer.Option(
|
|
296
|
+
"--project-root",
|
|
297
|
+
"-r",
|
|
298
|
+
help="Project root directory (default: current directory)",
|
|
299
|
+
),
|
|
300
|
+
] = Path("."),
|
|
301
|
+
output: Annotated[
|
|
302
|
+
str,
|
|
303
|
+
typer.Option(
|
|
304
|
+
"--output",
|
|
305
|
+
"-o",
|
|
306
|
+
help="Output format: human, json, or summary",
|
|
307
|
+
),
|
|
308
|
+
] = "human",
|
|
309
|
+
) -> None:
|
|
310
|
+
"""Build unified graph with discovered components.
|
|
311
|
+
|
|
312
|
+
Reads validated components from JSON and integrates them into the unified
|
|
313
|
+
context graph. Components become queryable via `raise context query`.
|
|
314
|
+
|
|
315
|
+
The graph is rebuilt from all sources (governance, memory, work, skills,
|
|
316
|
+
and components) and saved to `.raise/graph/unified.json`.
|
|
317
|
+
|
|
318
|
+
Examples:
|
|
319
|
+
# Build with default input file
|
|
320
|
+
raise discover build
|
|
321
|
+
|
|
322
|
+
# Build with custom input
|
|
323
|
+
raise discover build --input my-components.json
|
|
324
|
+
|
|
325
|
+
# Build and show JSON output
|
|
326
|
+
raise discover build --output json
|
|
327
|
+
"""
|
|
328
|
+
root = project_root.resolve()
|
|
329
|
+
|
|
330
|
+
# Resolve input file path
|
|
331
|
+
if input_file is None:
|
|
332
|
+
input_path = root / "work" / "discovery" / "components-validated.json"
|
|
333
|
+
else:
|
|
334
|
+
input_path = input_file.resolve()
|
|
335
|
+
|
|
336
|
+
# Check input file exists
|
|
337
|
+
if not input_path.exists():
|
|
338
|
+
cli_error(
|
|
339
|
+
f"Components file not found: {input_path}",
|
|
340
|
+
hint="Run /rai-discover-validate to generate validated components",
|
|
341
|
+
exit_code=4,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
# Load components to validate and count
|
|
345
|
+
component_count = 0
|
|
346
|
+
try:
|
|
347
|
+
data: dict[str, Any] = json.loads(input_path.read_text(encoding="utf-8"))
|
|
348
|
+
components: list[dict[str, Any]] = data.get("components", [])
|
|
349
|
+
component_count = len(components)
|
|
350
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
351
|
+
cli_error(f"Invalid JSON in {input_path}: {e}")
|
|
352
|
+
|
|
353
|
+
if component_count == 0:
|
|
354
|
+
cli_error(
|
|
355
|
+
"No components found in input file",
|
|
356
|
+
hint="Run /rai-discover-validate to validate components first",
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Build unified graph (includes components automatically)
|
|
360
|
+
from raise_cli.context.builder import GraphBuilder
|
|
361
|
+
|
|
362
|
+
builder = GraphBuilder(project_root=root)
|
|
363
|
+
graph = builder.build()
|
|
364
|
+
|
|
365
|
+
# Save graph via backend
|
|
366
|
+
from raise_cli.graph.backends import get_active_backend
|
|
367
|
+
|
|
368
|
+
graph_path = root / ".raise" / "graph" / "unified.json"
|
|
369
|
+
get_active_backend(graph_path).persist(graph)
|
|
370
|
+
|
|
371
|
+
# Count component nodes in graph
|
|
372
|
+
component_nodes = [n for n in graph.iter_concepts() if n.type == "component"]
|
|
373
|
+
components_in_graph = len(component_nodes)
|
|
374
|
+
|
|
375
|
+
# Build categories dict
|
|
376
|
+
categories: dict[str, int] = {}
|
|
377
|
+
for comp in component_nodes:
|
|
378
|
+
category = comp.metadata.get("category", "unknown")
|
|
379
|
+
categories[category] = categories.get(category, 0) + 1
|
|
380
|
+
|
|
381
|
+
# Build sample components list
|
|
382
|
+
sample_components = [
|
|
383
|
+
(
|
|
384
|
+
comp.metadata.get("name", comp.id),
|
|
385
|
+
comp.metadata.get("kind", ""),
|
|
386
|
+
comp.content[:60],
|
|
387
|
+
)
|
|
388
|
+
for comp in component_nodes[:3]
|
|
389
|
+
]
|
|
390
|
+
|
|
391
|
+
format_build_result(
|
|
392
|
+
input_path=input_path,
|
|
393
|
+
graph_path=graph_path,
|
|
394
|
+
component_count=component_count,
|
|
395
|
+
components_in_graph=components_in_graph,
|
|
396
|
+
node_count=graph.node_count,
|
|
397
|
+
edge_count=graph.edge_count,
|
|
398
|
+
categories=categories,
|
|
399
|
+
sample_components=sample_components,
|
|
400
|
+
output_format=output,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@discover_app.command("drift")
|
|
405
|
+
def drift_command(
|
|
406
|
+
path: Annotated[
|
|
407
|
+
Path | None,
|
|
408
|
+
typer.Argument(
|
|
409
|
+
help="Directory to scan for drift (default: src/)",
|
|
410
|
+
),
|
|
411
|
+
] = None,
|
|
412
|
+
project_root: Annotated[
|
|
413
|
+
Path,
|
|
414
|
+
typer.Option(
|
|
415
|
+
"--project-root",
|
|
416
|
+
"-r",
|
|
417
|
+
help="Project root directory (default: current directory)",
|
|
418
|
+
),
|
|
419
|
+
] = Path("."),
|
|
420
|
+
output: Annotated[
|
|
421
|
+
str,
|
|
422
|
+
typer.Option(
|
|
423
|
+
"--output",
|
|
424
|
+
"-o",
|
|
425
|
+
help="Output format: human, json, or summary",
|
|
426
|
+
),
|
|
427
|
+
] = "human",
|
|
428
|
+
) -> None:
|
|
429
|
+
"""Check for architectural drift against baseline components.
|
|
430
|
+
|
|
431
|
+
Compares scanned code against the validated component baseline to
|
|
432
|
+
identify potential architectural drift (files in wrong locations,
|
|
433
|
+
naming convention violations, missing documentation).
|
|
434
|
+
|
|
435
|
+
Exit codes:
|
|
436
|
+
0 - No drift detected
|
|
437
|
+
1 - Drift warnings found
|
|
438
|
+
|
|
439
|
+
Examples:
|
|
440
|
+
# Check entire project
|
|
441
|
+
raise discover drift
|
|
442
|
+
|
|
443
|
+
# Check specific directory
|
|
444
|
+
raise discover drift src/new_module/
|
|
445
|
+
|
|
446
|
+
# Output as JSON
|
|
447
|
+
raise discover drift --output json
|
|
448
|
+
"""
|
|
449
|
+
from raise_cli.discovery.drift import BaselineComponent, DriftWarning, detect_drift
|
|
450
|
+
|
|
451
|
+
root = project_root.resolve()
|
|
452
|
+
scan_path = path.resolve() if path else root / "src"
|
|
453
|
+
|
|
454
|
+
# Load baseline components
|
|
455
|
+
baseline_file = root / "work" / "discovery" / "components-validated.json"
|
|
456
|
+
|
|
457
|
+
if not baseline_file.exists():
|
|
458
|
+
if output == "json":
|
|
459
|
+
console.print_json(
|
|
460
|
+
json.dumps(
|
|
461
|
+
{
|
|
462
|
+
"status": "no_baseline",
|
|
463
|
+
"warnings": [],
|
|
464
|
+
"warning_count": 0,
|
|
465
|
+
"message": "No baseline components found",
|
|
466
|
+
}
|
|
467
|
+
)
|
|
468
|
+
)
|
|
469
|
+
else:
|
|
470
|
+
console.print(
|
|
471
|
+
"[yellow]No baseline components found.[/yellow]\n"
|
|
472
|
+
"[dim]Run /rai-discover-validate to create a baseline first.[/dim]"
|
|
473
|
+
)
|
|
474
|
+
raise typer.Exit(0)
|
|
475
|
+
|
|
476
|
+
# Load baseline
|
|
477
|
+
baseline: list[BaselineComponent] = []
|
|
478
|
+
try:
|
|
479
|
+
baseline_data: dict[str, Any] = json.loads(
|
|
480
|
+
baseline_file.read_text(encoding="utf-8")
|
|
481
|
+
)
|
|
482
|
+
baseline_dicts: list[dict[str, Any]] = baseline_data.get("components", [])
|
|
483
|
+
baseline = [BaselineComponent.model_validate(comp) for comp in baseline_dicts]
|
|
484
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
485
|
+
cli_error(f"Error reading baseline: {e}")
|
|
486
|
+
|
|
487
|
+
if not baseline:
|
|
488
|
+
if output == "json":
|
|
489
|
+
console.print_json(
|
|
490
|
+
json.dumps(
|
|
491
|
+
{
|
|
492
|
+
"status": "empty_baseline",
|
|
493
|
+
"warnings": [],
|
|
494
|
+
"warning_count": 0,
|
|
495
|
+
"message": "Baseline has no components",
|
|
496
|
+
}
|
|
497
|
+
)
|
|
498
|
+
)
|
|
499
|
+
else:
|
|
500
|
+
console.print(
|
|
501
|
+
"[yellow]Baseline has no components.[/yellow]\n"
|
|
502
|
+
"[dim]Run /rai-discover-validate to add components.[/dim]"
|
|
503
|
+
)
|
|
504
|
+
raise typer.Exit(0)
|
|
505
|
+
|
|
506
|
+
# Warn if baseline is too small for meaningful drift detection
|
|
507
|
+
min_baseline_size = 10
|
|
508
|
+
if len(baseline) < min_baseline_size and output == "human":
|
|
509
|
+
console.print(
|
|
510
|
+
f"[yellow]Note: Baseline has only {len(baseline)} component(s).[/yellow]\n"
|
|
511
|
+
f"[dim]Drift detection works best with {min_baseline_size}+ components "
|
|
512
|
+
"for meaningful patterns.[/dim]\n"
|
|
513
|
+
"[dim]Run /rai-discover-scan and /rai-discover-validate to expand the baseline.[/dim]\n"
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
# Scan for new symbols
|
|
517
|
+
if not scan_path.exists():
|
|
518
|
+
if output == "json":
|
|
519
|
+
console.print_json(
|
|
520
|
+
json.dumps(
|
|
521
|
+
{
|
|
522
|
+
"status": "no_source",
|
|
523
|
+
"warnings": [],
|
|
524
|
+
"warning_count": 0,
|
|
525
|
+
"message": f"Scan path not found: {scan_path}",
|
|
526
|
+
}
|
|
527
|
+
)
|
|
528
|
+
)
|
|
529
|
+
else:
|
|
530
|
+
console.print(f"[yellow]Scan path not found: {scan_path}[/yellow]")
|
|
531
|
+
raise typer.Exit(0)
|
|
532
|
+
|
|
533
|
+
scan_result = scan_directory(scan_path)
|
|
534
|
+
|
|
535
|
+
# Detect drift
|
|
536
|
+
warnings: list[DriftWarning] = detect_drift(
|
|
537
|
+
baseline=baseline,
|
|
538
|
+
scanned=scan_result.symbols,
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
# Output results
|
|
542
|
+
format_drift_result(
|
|
543
|
+
warnings=warnings,
|
|
544
|
+
files_scanned=scan_result.files_scanned,
|
|
545
|
+
symbols_checked=len(scan_result.symbols),
|
|
546
|
+
output_format=output,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
# Exit with 1 if warnings found
|
|
550
|
+
if warnings:
|
|
551
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""CLI commands for governance documentation via DocumentationTarget.
|
|
2
|
+
|
|
3
|
+
Provides the ``rai docs`` command group. All commands delegate to a
|
|
4
|
+
DocumentationTarget discovered via entry points. The target is resolved
|
|
5
|
+
automatically when exactly one is registered, or selected explicitly
|
|
6
|
+
via ``--target NAME``.
|
|
7
|
+
|
|
8
|
+
CLI owns domain logic (artifact type → local path convention).
|
|
9
|
+
Adapter owns platform config (space mapping, parent pages).
|
|
10
|
+
|
|
11
|
+
Architecture: E301 (Agent Tool Abstraction), ADR-034 (Governance)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Annotated
|
|
18
|
+
|
|
19
|
+
import typer
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
|
|
22
|
+
from raise_cli.cli.commands._resolve import resolve_docs_target
|
|
23
|
+
|
|
24
|
+
docs_app = typer.Typer(
|
|
25
|
+
name="docs",
|
|
26
|
+
help="Manage governance documentation via DocumentationTarget",
|
|
27
|
+
no_args_is_help=True,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
console = Console()
|
|
31
|
+
|
|
32
|
+
# Common option for target override (D5)
|
|
33
|
+
TargetOption = Annotated[
|
|
34
|
+
str | None,
|
|
35
|
+
typer.Option(
|
|
36
|
+
"--target", "-t", help="Target name override (auto-detect if omitted)"
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# Convention: governance artifacts live at governance/{type}.md
|
|
41
|
+
GOVERNANCE_DIR = "governance"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _resolve_artifact_path(artifact_type: str) -> Path:
|
|
45
|
+
"""Resolve artifact type to local file path by convention.
|
|
46
|
+
|
|
47
|
+
Convention: ``governance/{artifact_type}.md``
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
artifact_type: Governance artifact type (e.g., "roadmap", "adr").
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Path to the governance file.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
typer.Exit: If the file does not exist.
|
|
57
|
+
"""
|
|
58
|
+
path = Path(GOVERNANCE_DIR) / f"{artifact_type}.md"
|
|
59
|
+
if not path.exists():
|
|
60
|
+
console.print(f"[red]Error:[/red] File not found: {path}")
|
|
61
|
+
raise typer.Exit(1)
|
|
62
|
+
return path
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@docs_app.command()
|
|
66
|
+
def publish(
|
|
67
|
+
artifact_type: Annotated[
|
|
68
|
+
str, typer.Argument(help="Governance artifact type (e.g., roadmap, adr)")
|
|
69
|
+
],
|
|
70
|
+
title: Annotated[
|
|
71
|
+
str | None, typer.Option("--title", help="Page title (default: artifact type)")
|
|
72
|
+
] = None,
|
|
73
|
+
target: TargetOption = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Publish a governance artifact to a documentation target."""
|
|
76
|
+
path = _resolve_artifact_path(artifact_type)
|
|
77
|
+
content = path.read_text(encoding="utf-8")
|
|
78
|
+
doc_target = resolve_docs_target(target)
|
|
79
|
+
|
|
80
|
+
page_title = title or artifact_type
|
|
81
|
+
metadata = {"title": page_title, "path": str(path)}
|
|
82
|
+
|
|
83
|
+
result = doc_target.publish(
|
|
84
|
+
doc_type=artifact_type, content=content, metadata=metadata
|
|
85
|
+
)
|
|
86
|
+
if result.success:
|
|
87
|
+
console.print(f"Published: {artifact_type} → {result.url}")
|
|
88
|
+
else:
|
|
89
|
+
console.print(f"[red]Error:[/red] {result.message}")
|
|
90
|
+
raise typer.Exit(1)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@docs_app.command()
|
|
94
|
+
def get(
|
|
95
|
+
identifier: Annotated[str, typer.Argument(help="Page ID on the remote target")],
|
|
96
|
+
target: TargetOption = None,
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Retrieve a page from the documentation target."""
|
|
99
|
+
doc_target = resolve_docs_target(target)
|
|
100
|
+
page = doc_target.get_page(identifier)
|
|
101
|
+
# Compact header + content
|
|
102
|
+
header_parts = [f"# {page.title}"]
|
|
103
|
+
if page.space_key:
|
|
104
|
+
header_parts.append(f"Space: {page.space_key}")
|
|
105
|
+
if page.version > 1:
|
|
106
|
+
header_parts.append(f"Version: {page.version}")
|
|
107
|
+
if page.url:
|
|
108
|
+
header_parts.append(page.url)
|
|
109
|
+
|
|
110
|
+
console.print(header_parts[0])
|
|
111
|
+
if len(header_parts) > 1:
|
|
112
|
+
console.print(" | ".join(header_parts[1:]))
|
|
113
|
+
console.print()
|
|
114
|
+
console.print(page.content)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@docs_app.command()
|
|
118
|
+
def search(
|
|
119
|
+
query: Annotated[str, typer.Argument(help="Search query")],
|
|
120
|
+
limit: Annotated[int, typer.Option("--limit", "-n", help="Max results")] = 10,
|
|
121
|
+
target: TargetOption = None,
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Search documentation pages on the remote target."""
|
|
124
|
+
doc_target = resolve_docs_target(target)
|
|
125
|
+
results = doc_target.search(query, limit=limit)
|
|
126
|
+
if not results:
|
|
127
|
+
console.print("No results.")
|
|
128
|
+
return
|
|
129
|
+
for page in results:
|
|
130
|
+
console.print(f"{page.id:<8} {page.space_key:<8} {page.title}")
|