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/cli/main.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Main CLI application entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from typing import Annotated, Literal
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from raise_cli import __version__
|
|
12
|
+
from raise_cli.cli.commands.adapters import adapters_app
|
|
13
|
+
from raise_cli.cli.commands.artifact import artifact_app
|
|
14
|
+
from raise_cli.cli.commands.backlog import backlog_app
|
|
15
|
+
from raise_cli.cli.commands.base import base_app
|
|
16
|
+
from raise_cli.cli.commands.discover import discover_app
|
|
17
|
+
from raise_cli.cli.commands.docs import docs_app
|
|
18
|
+
from raise_cli.cli.commands.doctor import doctor_app
|
|
19
|
+
from raise_cli.cli.commands.gate import gate_app
|
|
20
|
+
from raise_cli.cli.commands.graph import graph_app
|
|
21
|
+
from raise_cli.cli.commands.info import info_command
|
|
22
|
+
from raise_cli.cli.commands.init import init_command
|
|
23
|
+
from raise_cli.cli.commands.mcp import mcp_app
|
|
24
|
+
from raise_cli.cli.commands.memory import memory_app
|
|
25
|
+
from raise_cli.cli.commands.pattern import pattern_app
|
|
26
|
+
from raise_cli.cli.commands.profile import profile_app
|
|
27
|
+
from raise_cli.cli.commands.publish import publish_app
|
|
28
|
+
from raise_cli.cli.commands.release import release_app
|
|
29
|
+
from raise_cli.cli.commands.session import session_app
|
|
30
|
+
from raise_cli.cli.commands.signal import signal_app
|
|
31
|
+
from raise_cli.cli.commands.skill import skill_app
|
|
32
|
+
from raise_cli.config import RaiSettings
|
|
33
|
+
|
|
34
|
+
# Module-level state for error handling
|
|
35
|
+
_current_output_format: Literal["human", "json", "table"] = "human"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_output_format() -> Literal["human", "json", "table"]:
|
|
39
|
+
"""Get the current output format.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
The output format string ("human", "json", or "table").
|
|
43
|
+
"""
|
|
44
|
+
return _current_output_format
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
app = typer.Typer(
|
|
48
|
+
name="rai",
|
|
49
|
+
help="RaiSE CLI - Reliable AI Software Engineering",
|
|
50
|
+
no_args_is_help=True,
|
|
51
|
+
add_completion=False,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Register command groups
|
|
55
|
+
app.add_typer(adapters_app, name="adapter")
|
|
56
|
+
app.add_typer(artifact_app, name="artifact")
|
|
57
|
+
app.add_typer(backlog_app, name="backlog")
|
|
58
|
+
app.add_typer(base_app, name="base")
|
|
59
|
+
app.add_typer(docs_app, name="docs")
|
|
60
|
+
app.add_typer(discover_app, name="discover")
|
|
61
|
+
app.add_typer(doctor_app, name="doctor")
|
|
62
|
+
app.add_typer(gate_app, name="gate")
|
|
63
|
+
app.add_typer(graph_app, name="graph")
|
|
64
|
+
app.add_typer(mcp_app, name="mcp")
|
|
65
|
+
app.add_typer(memory_app, name="memory")
|
|
66
|
+
app.add_typer(pattern_app, name="pattern")
|
|
67
|
+
app.add_typer(profile_app, name="profile")
|
|
68
|
+
app.add_typer(publish_app, name="publish")
|
|
69
|
+
app.add_typer(release_app, name="release")
|
|
70
|
+
app.add_typer(session_app, name="session")
|
|
71
|
+
app.add_typer(signal_app, name="signal")
|
|
72
|
+
app.add_typer(skill_app, name="skill")
|
|
73
|
+
|
|
74
|
+
# Register standalone commands
|
|
75
|
+
app.command("info")(info_command)
|
|
76
|
+
app.command("init")(init_command)
|
|
77
|
+
|
|
78
|
+
console = Console()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class OutputFormat(StrEnum):
|
|
82
|
+
"""Output format options."""
|
|
83
|
+
|
|
84
|
+
human = "human"
|
|
85
|
+
json = "json"
|
|
86
|
+
table = "table"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def version_callback(value: bool) -> None:
|
|
90
|
+
"""Print version and exit."""
|
|
91
|
+
if value:
|
|
92
|
+
console.print(f"raise-cli version {__version__}")
|
|
93
|
+
raise typer.Exit(0)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.callback()
|
|
97
|
+
def main(
|
|
98
|
+
ctx: typer.Context,
|
|
99
|
+
version: Annotated[
|
|
100
|
+
bool,
|
|
101
|
+
typer.Option(
|
|
102
|
+
"--version",
|
|
103
|
+
"-V",
|
|
104
|
+
callback=version_callback,
|
|
105
|
+
is_eager=True,
|
|
106
|
+
help="Show version and exit",
|
|
107
|
+
),
|
|
108
|
+
] = False,
|
|
109
|
+
format: Annotated[
|
|
110
|
+
OutputFormat,
|
|
111
|
+
typer.Option(
|
|
112
|
+
"--format",
|
|
113
|
+
"-f",
|
|
114
|
+
help="Output format (human, json, table)",
|
|
115
|
+
),
|
|
116
|
+
] = OutputFormat.human,
|
|
117
|
+
verbose: Annotated[
|
|
118
|
+
int,
|
|
119
|
+
typer.Option(
|
|
120
|
+
"--verbose",
|
|
121
|
+
"-v",
|
|
122
|
+
count=True,
|
|
123
|
+
help="Increase verbosity (-v, -vv, -vvv)",
|
|
124
|
+
),
|
|
125
|
+
] = 0,
|
|
126
|
+
quiet: Annotated[
|
|
127
|
+
bool,
|
|
128
|
+
typer.Option(
|
|
129
|
+
"--quiet",
|
|
130
|
+
"-q",
|
|
131
|
+
help="Suppress non-error output",
|
|
132
|
+
),
|
|
133
|
+
] = False,
|
|
134
|
+
) -> None:
|
|
135
|
+
"""RaiSE CLI - Reliable AI Software Engineering governance framework.
|
|
136
|
+
|
|
137
|
+
Global options apply to all commands and control output format and verbosity.
|
|
138
|
+
"""
|
|
139
|
+
global _current_output_format # noqa: PLW0603
|
|
140
|
+
_current_output_format = format.value # type: ignore[assignment]
|
|
141
|
+
|
|
142
|
+
# Calculate verbosity from flags
|
|
143
|
+
verbosity = -1 if quiet else min(verbose, 3)
|
|
144
|
+
|
|
145
|
+
# Create settings with CLI overrides (highest priority)
|
|
146
|
+
settings = RaiSettings(
|
|
147
|
+
output_format=format.value, # type: ignore[arg-type]
|
|
148
|
+
verbosity=verbosity,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Store in context for subcommands
|
|
152
|
+
ctx.ensure_object(dict)
|
|
153
|
+
ctx.obj["settings"] = settings
|
|
154
|
+
|
|
155
|
+
# Backward compatibility: keep individual values in ctx.obj
|
|
156
|
+
# (Can be removed once all commands migrate to using settings)
|
|
157
|
+
ctx.obj["format"] = format.value
|
|
158
|
+
ctx.obj["verbosity"] = verbosity
|
|
159
|
+
ctx.obj["quiet"] = quiet
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
app()
|
raise_cli/compat.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Cross-platform compatibility layer.
|
|
2
|
+
|
|
3
|
+
Centralizes all platform-specific code so the rest of the codebase
|
|
4
|
+
never checks sys.platform directly. Pattern used by pip, poetry, virtualenv.
|
|
5
|
+
|
|
6
|
+
All platform guards live here. Import from compat, not from fcntl/msvcrt.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import IO
|
|
14
|
+
|
|
15
|
+
IS_WINDOWS = sys.platform == "win32"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def file_lock(f: IO[str], *, exclusive: bool = True) -> None:
|
|
19
|
+
"""Acquire a file lock. Uses fcntl on Unix, msvcrt on Windows."""
|
|
20
|
+
if sys.platform == "win32":
|
|
21
|
+
import msvcrt
|
|
22
|
+
|
|
23
|
+
msvcrt.locking(f.fileno(), msvcrt.LK_LOCK, 1)
|
|
24
|
+
else:
|
|
25
|
+
import fcntl
|
|
26
|
+
|
|
27
|
+
fcntl.flock(f.fileno(), fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def file_unlock(f: IO[str]) -> None:
|
|
31
|
+
"""Release a file lock. Uses fcntl on Unix, msvcrt on Windows."""
|
|
32
|
+
if sys.platform == "win32":
|
|
33
|
+
import msvcrt
|
|
34
|
+
|
|
35
|
+
msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, 1)
|
|
36
|
+
else:
|
|
37
|
+
import fcntl
|
|
38
|
+
|
|
39
|
+
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def portable_path(path: Path, relative_to: Path) -> str:
|
|
43
|
+
"""Return forward-slash relative path string for serialization.
|
|
44
|
+
|
|
45
|
+
Always uses forward slashes regardless of OS, ensuring consistent
|
|
46
|
+
path strings in JSON, graph data, and other serialized formats.
|
|
47
|
+
"""
|
|
48
|
+
return path.relative_to(relative_to).as_posix()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def to_file_uri(path: Path) -> str:
|
|
52
|
+
"""Return correct file:// URI on any platform.
|
|
53
|
+
|
|
54
|
+
Uses pathlib's as_uri() which handles Windows drive letters correctly.
|
|
55
|
+
"""
|
|
56
|
+
return path.resolve().as_uri()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def secure_permissions(path: Path) -> None:
|
|
60
|
+
"""Set restrictive file permissions (0o600). No-op on Windows.
|
|
61
|
+
|
|
62
|
+
On Windows, POSIX chmod has no effect. For true Windows ACL
|
|
63
|
+
restriction, icacls would be needed — deferred until required.
|
|
64
|
+
"""
|
|
65
|
+
if not IS_WINDOWS:
|
|
66
|
+
path.chmod(0o600)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Configuration module for raise-cli.
|
|
2
|
+
|
|
3
|
+
Provides configuration settings and XDG-compliant directory helpers.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from raise_cli.config.agents import (
|
|
9
|
+
BUILTIN_AGENTS,
|
|
10
|
+
AgentChoice,
|
|
11
|
+
AgentConfig,
|
|
12
|
+
BuiltinAgentType,
|
|
13
|
+
get_agent_config,
|
|
14
|
+
)
|
|
15
|
+
from raise_cli.config.paths import get_cache_dir, get_config_dir, get_data_dir
|
|
16
|
+
from raise_cli.config.settings import RaiSettings
|
|
17
|
+
|
|
18
|
+
# Backward-compat aliases
|
|
19
|
+
IDE_CONFIGS = BUILTIN_AGENTS
|
|
20
|
+
IdeChoice = AgentChoice
|
|
21
|
+
IdeConfig = AgentConfig
|
|
22
|
+
IdeType = BuiltinAgentType
|
|
23
|
+
get_ide_config = get_agent_config
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"BUILTIN_AGENTS",
|
|
27
|
+
"AgentChoice",
|
|
28
|
+
"AgentConfig",
|
|
29
|
+
"BuiltinAgentType",
|
|
30
|
+
"get_agent_config",
|
|
31
|
+
# Backward-compat
|
|
32
|
+
"IDE_CONFIGS",
|
|
33
|
+
"IdeChoice",
|
|
34
|
+
"IdeConfig",
|
|
35
|
+
"IdeType",
|
|
36
|
+
"get_ide_config",
|
|
37
|
+
"get_cache_dir",
|
|
38
|
+
"get_config_dir",
|
|
39
|
+
"get_data_dir",
|
|
40
|
+
"RaiSettings",
|
|
41
|
+
]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""AgentPlugin protocol and default implementation.
|
|
2
|
+
|
|
3
|
+
Defines the extensibility interface for agent-specific transformations.
|
|
4
|
+
Plugins can transform instructions, skills, and run post-init hooks.
|
|
5
|
+
|
|
6
|
+
Architecture: ADR-032 (Multi-agent skill distribution).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Protocol, runtime_checkable
|
|
13
|
+
|
|
14
|
+
from raise_cli.config.agents import AgentConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class AgentPlugin(Protocol):
|
|
19
|
+
"""Protocol for custom agent connectors.
|
|
20
|
+
|
|
21
|
+
Implementors add agent-specific transformation logic without modifying
|
|
22
|
+
the raise-cli engine. A plugin only needs to implement the methods it uses —
|
|
23
|
+
duck typing, no forced inheritance.
|
|
24
|
+
|
|
25
|
+
Example (minimal plugin):
|
|
26
|
+
class MyPlugin:
|
|
27
|
+
def transform_instructions(self, content, config):
|
|
28
|
+
return f"<!-- {config.name} -->\\n{content}"
|
|
29
|
+
|
|
30
|
+
def transform_skill(self, frontmatter, body, config):
|
|
31
|
+
return frontmatter, body
|
|
32
|
+
|
|
33
|
+
def post_init(self, project_root, config):
|
|
34
|
+
return []
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def transform_instructions(self, content: str, config: AgentConfig) -> str:
|
|
38
|
+
"""Transform generated instructions for this target.
|
|
39
|
+
|
|
40
|
+
Called after the instructions content is generated, before writing to disk.
|
|
41
|
+
Default: pass-through (return content unchanged).
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
content: Generated markdown instructions content.
|
|
45
|
+
config: Target agent configuration.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Transformed content string.
|
|
49
|
+
"""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
def transform_skill(
|
|
53
|
+
self, frontmatter: dict[str, Any], body: str, config: AgentConfig
|
|
54
|
+
) -> tuple[dict[str, Any], str]:
|
|
55
|
+
"""Transform a SKILL.md for this target.
|
|
56
|
+
|
|
57
|
+
Called for each skill file during scaffolding.
|
|
58
|
+
Default: pass-through (return unchanged).
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
frontmatter: Parsed YAML frontmatter dict.
|
|
62
|
+
body: Skill body markdown text.
|
|
63
|
+
config: Target agent configuration.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Tuple of (transformed_frontmatter, transformed_body).
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
def post_init(self, project_root: Path, config: AgentConfig) -> list[str]:
|
|
71
|
+
"""Run after all files are generated.
|
|
72
|
+
|
|
73
|
+
Called once at the end of `rai init` for this agent.
|
|
74
|
+
Default: no-op (return empty list).
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
project_root: Project root directory.
|
|
78
|
+
config: Target agent configuration.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
List of file paths created by this hook.
|
|
82
|
+
"""
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class DefaultAgentPlugin:
|
|
87
|
+
"""Default no-op plugin used when no plugin is specified.
|
|
88
|
+
|
|
89
|
+
All methods are pass-through — skills and instructions are copied
|
|
90
|
+
as-is with no transformation. Four of five built-in agents use this.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def transform_instructions(self, content: str, config: AgentConfig) -> str:
|
|
94
|
+
"""Return content unchanged."""
|
|
95
|
+
return content
|
|
96
|
+
|
|
97
|
+
def transform_skill(
|
|
98
|
+
self, frontmatter: dict[str, Any], body: str, config: AgentConfig
|
|
99
|
+
) -> tuple[dict[str, Any], str]:
|
|
100
|
+
"""Return frontmatter and body unchanged (copies, not references)."""
|
|
101
|
+
return dict(frontmatter), body
|
|
102
|
+
|
|
103
|
+
def post_init(self, project_root: Path, config: AgentConfig) -> list[str]:
|
|
104
|
+
"""No-op — return empty list."""
|
|
105
|
+
return []
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Agent registry — 3-tier YAML loading with override precedence.
|
|
2
|
+
|
|
3
|
+
Loads agent configurations from:
|
|
4
|
+
1. Built-in YAML files (bundled in raise_cli.agents package)
|
|
5
|
+
2. Project-level .raise/agents/*.yaml
|
|
6
|
+
3. User-level ~/.rai/agents/*.yaml
|
|
7
|
+
|
|
8
|
+
Last-wins precedence: user > project > built-in.
|
|
9
|
+
|
|
10
|
+
Architecture: ADR-032 (Multi-agent skill distribution).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import importlib
|
|
16
|
+
import logging
|
|
17
|
+
from importlib.resources import files
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, cast
|
|
20
|
+
|
|
21
|
+
import yaml
|
|
22
|
+
|
|
23
|
+
from raise_cli.config.agent_plugin import AgentPlugin, DefaultAgentPlugin
|
|
24
|
+
from raise_cli.config.agents import AgentConfig
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
_DEFAULT_PLUGIN = DefaultAgentPlugin()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _parse_agent_config(data: dict[str, Any]) -> AgentConfig:
|
|
32
|
+
"""Parse a YAML dict into an AgentConfig model."""
|
|
33
|
+
return AgentConfig(
|
|
34
|
+
name=data["name"],
|
|
35
|
+
agent_type=data["agent_type"],
|
|
36
|
+
instructions_file=data["instructions_file"],
|
|
37
|
+
skills_dir=data.get("skills_dir"),
|
|
38
|
+
workflows_dir=data.get("workflows_dir"),
|
|
39
|
+
detection_markers=data.get("detection_markers", []),
|
|
40
|
+
plugin=data.get("plugin"),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _load_yaml_dir(directory: Path) -> dict[str, AgentConfig]:
|
|
45
|
+
"""Load all *.yaml files from a directory into agent configs."""
|
|
46
|
+
configs: dict[str, AgentConfig] = {}
|
|
47
|
+
if not directory.exists():
|
|
48
|
+
return configs
|
|
49
|
+
for yaml_file in sorted(directory.glob("*.yaml")):
|
|
50
|
+
try:
|
|
51
|
+
raw = yaml.safe_load(yaml_file.read_text(encoding="utf-8"))
|
|
52
|
+
if not isinstance(raw, dict):
|
|
53
|
+
logger.warning("Skipping %s: not a YAML mapping", yaml_file)
|
|
54
|
+
continue
|
|
55
|
+
config = _parse_agent_config(cast(dict[str, Any], raw))
|
|
56
|
+
configs[config.agent_type] = config
|
|
57
|
+
except (yaml.YAMLError, KeyError, TypeError, ValueError) as e:
|
|
58
|
+
logger.warning("Skipping %s: %s", yaml_file, e)
|
|
59
|
+
return configs
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _load_builtin_agents() -> dict[str, AgentConfig]:
|
|
63
|
+
"""Load agent configs from the bundled raise_cli.agents package."""
|
|
64
|
+
configs: dict[str, AgentConfig] = {}
|
|
65
|
+
agents_pkg = files("raise_cli.agents")
|
|
66
|
+
for entry in agents_pkg.iterdir():
|
|
67
|
+
if not entry.name.endswith(".yaml"):
|
|
68
|
+
continue
|
|
69
|
+
try:
|
|
70
|
+
raw = yaml.safe_load(entry.read_text(encoding="utf-8"))
|
|
71
|
+
if not isinstance(raw, dict):
|
|
72
|
+
logger.warning("Skipping built-in %s: not a YAML mapping", entry.name)
|
|
73
|
+
continue
|
|
74
|
+
config = _parse_agent_config(cast(dict[str, Any], raw))
|
|
75
|
+
configs[config.agent_type] = config
|
|
76
|
+
except (yaml.YAMLError, KeyError, TypeError, ValueError) as e:
|
|
77
|
+
logger.warning("Skipping built-in %s: %s", entry.name, e)
|
|
78
|
+
return configs
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _resolve_plugin(plugin_path: str | None) -> AgentPlugin:
|
|
82
|
+
"""Resolve a plugin module path string to an AgentPlugin instance.
|
|
83
|
+
|
|
84
|
+
Expects the module to contain a class with the same name as the last
|
|
85
|
+
segment of the module path (PascalCase), or a class named 'Plugin'.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
plugin_path: Module path like "raise_cli.agents.copilot_plugin",
|
|
89
|
+
or None for default pass-through.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
AgentPlugin instance.
|
|
93
|
+
"""
|
|
94
|
+
if not plugin_path:
|
|
95
|
+
return _DEFAULT_PLUGIN
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
module = importlib.import_module(plugin_path)
|
|
99
|
+
except ImportError as e:
|
|
100
|
+
logger.warning("Could not import plugin %r: %s — using default", plugin_path, e)
|
|
101
|
+
return _DEFAULT_PLUGIN
|
|
102
|
+
|
|
103
|
+
# Convention: look for class named Plugin, or derive from module name
|
|
104
|
+
# e.g. "copilot_plugin" → "CopilotPlugin"
|
|
105
|
+
segments = plugin_path.split(".")
|
|
106
|
+
module_name = segments[-1] # e.g. "copilot_plugin"
|
|
107
|
+
class_name = "".join(part.capitalize() for part in module_name.split("_"))
|
|
108
|
+
# e.g. "CopilotPlugin"
|
|
109
|
+
|
|
110
|
+
cls = getattr(module, class_name, None) or getattr(module, "Plugin", None)
|
|
111
|
+
if cls is None:
|
|
112
|
+
logger.warning(
|
|
113
|
+
"Plugin module %r has no class %r or 'Plugin' — using default",
|
|
114
|
+
plugin_path,
|
|
115
|
+
class_name,
|
|
116
|
+
)
|
|
117
|
+
return _DEFAULT_PLUGIN
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
return cls()
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.warning(
|
|
123
|
+
"Could not instantiate %r from %r: %s", class_name, plugin_path, e
|
|
124
|
+
)
|
|
125
|
+
return _DEFAULT_PLUGIN
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class AgentRegistry:
|
|
129
|
+
"""Registry of agent configurations loaded from 3-tier YAML sources.
|
|
130
|
+
|
|
131
|
+
Provides config and plugin lookup by agent_type key.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def __init__(self, configs: dict[str, AgentConfig]) -> None:
|
|
135
|
+
self._configs = configs
|
|
136
|
+
self._plugins: dict[str, AgentPlugin] = {}
|
|
137
|
+
|
|
138
|
+
# ------------------------------------------------------------------
|
|
139
|
+
# Public API
|
|
140
|
+
# ------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
def get_config(self, agent_type: str) -> AgentConfig:
|
|
143
|
+
"""Return AgentConfig for the given agent_type.
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
KeyError: If agent_type is not registered.
|
|
147
|
+
"""
|
|
148
|
+
return self._configs[agent_type]
|
|
149
|
+
|
|
150
|
+
def get_plugin(self, agent_type: str) -> AgentPlugin:
|
|
151
|
+
"""Return AgentPlugin for the given agent_type (cached after first call).
|
|
152
|
+
|
|
153
|
+
Falls back to DefaultAgentPlugin if no plugin specified or load fails.
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
KeyError: If agent_type is not registered.
|
|
157
|
+
"""
|
|
158
|
+
if agent_type not in self._configs:
|
|
159
|
+
raise KeyError(agent_type)
|
|
160
|
+
if agent_type not in self._plugins:
|
|
161
|
+
config = self._configs[agent_type]
|
|
162
|
+
self._plugins[agent_type] = _resolve_plugin(config.plugin)
|
|
163
|
+
return self._plugins[agent_type]
|
|
164
|
+
|
|
165
|
+
def list_agents(self) -> list[str]:
|
|
166
|
+
"""Return sorted list of all registered agent_type keys."""
|
|
167
|
+
return sorted(self._configs.keys())
|
|
168
|
+
|
|
169
|
+
def detect_agents(
|
|
170
|
+
self, project_root: Path, user_home: Path | None = None
|
|
171
|
+
) -> list[str]:
|
|
172
|
+
"""Detect which registered agents have marker files in project_root.
|
|
173
|
+
|
|
174
|
+
Checks each agent's detection_markers list; stops at first match per
|
|
175
|
+
agent. Markers starting with ``~/`` are resolved against user_home
|
|
176
|
+
(defaults to Path.home()) instead of project_root.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
project_root: Directory to check for project-relative marker paths.
|
|
180
|
+
user_home: Home directory for ``~/`` markers (defaults to Path.home()).
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Sorted list of detected agent_type strings.
|
|
184
|
+
"""
|
|
185
|
+
home = user_home if user_home is not None else Path.home()
|
|
186
|
+
detected: list[str] = []
|
|
187
|
+
for agent_type, config in self._configs.items():
|
|
188
|
+
for marker in config.detection_markers:
|
|
189
|
+
if marker.startswith("~/"):
|
|
190
|
+
check_path = home / marker[2:]
|
|
191
|
+
else:
|
|
192
|
+
check_path = project_root / marker
|
|
193
|
+
if check_path.exists():
|
|
194
|
+
detected.append(agent_type)
|
|
195
|
+
break
|
|
196
|
+
return sorted(detected)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def load_registry(
|
|
200
|
+
project_root: Path | None = None,
|
|
201
|
+
user_home: Path | None = None,
|
|
202
|
+
) -> AgentRegistry:
|
|
203
|
+
"""Load the 3-tier agent registry.
|
|
204
|
+
|
|
205
|
+
Loading order (last-wins):
|
|
206
|
+
1. Built-in YAML files bundled in raise_cli.agents package
|
|
207
|
+
2. Project-level .raise/agents/*.yaml (if project_root provided)
|
|
208
|
+
3. User-level ~/.rai/agents/*.yaml (if user_home provided; defaults to Path.home())
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
project_root: Project root directory for .raise/agents/ lookup.
|
|
212
|
+
user_home: Home directory for ~/.rai/agents/ lookup.
|
|
213
|
+
Defaults to Path.home() if not provided.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
AgentRegistry populated with merged configs.
|
|
217
|
+
"""
|
|
218
|
+
# Tier 1: built-in
|
|
219
|
+
configs: dict[str, AgentConfig] = _load_builtin_agents()
|
|
220
|
+
|
|
221
|
+
# Tier 2: project-level
|
|
222
|
+
if project_root is not None:
|
|
223
|
+
project_agents_dir = project_root / ".raise" / "agents"
|
|
224
|
+
project_configs = _load_yaml_dir(project_agents_dir)
|
|
225
|
+
configs.update(project_configs)
|
|
226
|
+
|
|
227
|
+
# Tier 3: user-level
|
|
228
|
+
home = user_home if user_home is not None else Path.home()
|
|
229
|
+
user_agents_dir = home / ".rai" / "agents"
|
|
230
|
+
user_configs = _load_yaml_dir(user_agents_dir)
|
|
231
|
+
configs.update(user_configs)
|
|
232
|
+
|
|
233
|
+
return AgentRegistry(configs)
|