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,143 @@
|
|
|
1
|
+
# Reference YAML adapter config: GitHub Issues via MCP
|
|
2
|
+
#
|
|
3
|
+
# This file demonstrates how to integrate an MCP server with the
|
|
4
|
+
# declarative adapter framework. Copy and adapt for your own server.
|
|
5
|
+
#
|
|
6
|
+
# Architecture: ADR-041, E337
|
|
7
|
+
|
|
8
|
+
# --- Adapter identity ---
|
|
9
|
+
# name: Used with --adapter flag (e.g., rai backlog search --adapter github)
|
|
10
|
+
# protocol: "pm" (ProjectManagement) or "docs" (Documentation)
|
|
11
|
+
# description: Human-readable, shown in `rai adapter list`
|
|
12
|
+
adapter:
|
|
13
|
+
name: github
|
|
14
|
+
protocol: pm
|
|
15
|
+
description: "GitHub Issues via mcp-github"
|
|
16
|
+
|
|
17
|
+
# --- MCP server connection ---
|
|
18
|
+
# command: Executable to launch the MCP server subprocess
|
|
19
|
+
# args: Command-line arguments passed to the server
|
|
20
|
+
# env: Environment variable names to forward to the subprocess
|
|
21
|
+
# (values read from the current environment at runtime)
|
|
22
|
+
server:
|
|
23
|
+
command: uvx
|
|
24
|
+
args: [mcp-github]
|
|
25
|
+
env: [GITHUB_TOKEN]
|
|
26
|
+
|
|
27
|
+
# --- Protocol method mappings ---
|
|
28
|
+
# Each key is a protocol method name from AsyncProjectManagementAdapter.
|
|
29
|
+
# Set to null for methods the MCP server doesn't support.
|
|
30
|
+
#
|
|
31
|
+
# Per method:
|
|
32
|
+
# tool: MCP tool name to call
|
|
33
|
+
# args: Argument name → expression template
|
|
34
|
+
# Templates use {{ var }} syntax with dot-access and filters:
|
|
35
|
+
# {{ issue.summary }} — dot-access into nested objects
|
|
36
|
+
# {{ data.number | str }} — type coercion filter
|
|
37
|
+
# {{ data.url | default:"" }} — default value filter
|
|
38
|
+
# {{ data.labels | pluck:"name" }} — extract field from list items
|
|
39
|
+
# response: How to parse the MCP tool response
|
|
40
|
+
# fields: Field name → expression template (for single-item responses)
|
|
41
|
+
# items_path: Dot-path to list in response data (for list responses)
|
|
42
|
+
methods:
|
|
43
|
+
|
|
44
|
+
# --- CRUD ---
|
|
45
|
+
|
|
46
|
+
create_issue:
|
|
47
|
+
tool: github_create_issue
|
|
48
|
+
args:
|
|
49
|
+
title: "{{ issue.summary }}"
|
|
50
|
+
body: "{{ issue.description | default:'' }}"
|
|
51
|
+
repo: "{{ project_key }}"
|
|
52
|
+
labels: "{{ issue.labels | default:'' }}"
|
|
53
|
+
response:
|
|
54
|
+
fields:
|
|
55
|
+
key: "{{ data.number | str }}"
|
|
56
|
+
url: "{{ data.html_url }}"
|
|
57
|
+
|
|
58
|
+
get_issue:
|
|
59
|
+
tool: github_get_issue
|
|
60
|
+
args:
|
|
61
|
+
issue_number: "{{ key }}"
|
|
62
|
+
response:
|
|
63
|
+
fields:
|
|
64
|
+
key: "{{ data.number | str }}"
|
|
65
|
+
summary: "{{ data.title }}"
|
|
66
|
+
status: "{{ data.state }}"
|
|
67
|
+
issue_type: "{{ data.type | default:'Issue' }}"
|
|
68
|
+
description: "{{ data.body | default:'' }}"
|
|
69
|
+
|
|
70
|
+
update_issue:
|
|
71
|
+
tool: github_update_issue
|
|
72
|
+
args:
|
|
73
|
+
issue_number: "{{ key }}"
|
|
74
|
+
title: "{{ fields.summary | default:'' }}"
|
|
75
|
+
body: "{{ fields.description | default:'' }}"
|
|
76
|
+
response:
|
|
77
|
+
fields:
|
|
78
|
+
key: "{{ data.number | str }}"
|
|
79
|
+
url: "{{ data.html_url }}"
|
|
80
|
+
|
|
81
|
+
transition_issue:
|
|
82
|
+
tool: github_update_issue
|
|
83
|
+
args:
|
|
84
|
+
issue_number: "{{ key }}"
|
|
85
|
+
state: "{{ status }}"
|
|
86
|
+
response:
|
|
87
|
+
fields:
|
|
88
|
+
key: "{{ data.number | str }}"
|
|
89
|
+
url: "{{ data.html_url }}"
|
|
90
|
+
|
|
91
|
+
# --- Batch ---
|
|
92
|
+
# batch_transition is auto-looped by the adapter (calls transition_issue N times).
|
|
93
|
+
# No MCP tool needed — set to null.
|
|
94
|
+
batch_transition: null
|
|
95
|
+
|
|
96
|
+
# --- Relationships ---
|
|
97
|
+
# GitHub doesn't have native issue linking via MCP tools.
|
|
98
|
+
link_to_parent: null
|
|
99
|
+
link_issues: null
|
|
100
|
+
|
|
101
|
+
# --- Comments ---
|
|
102
|
+
|
|
103
|
+
add_comment:
|
|
104
|
+
tool: github_add_comment
|
|
105
|
+
args:
|
|
106
|
+
issue_number: "{{ key }}"
|
|
107
|
+
body: "{{ body }}"
|
|
108
|
+
response:
|
|
109
|
+
fields:
|
|
110
|
+
id: "{{ data.id | str }}"
|
|
111
|
+
url: "{{ data.html_url }}"
|
|
112
|
+
|
|
113
|
+
get_comments:
|
|
114
|
+
tool: github_list_comments
|
|
115
|
+
args:
|
|
116
|
+
issue_number: "{{ key }}"
|
|
117
|
+
per_page: "{{ limit }}"
|
|
118
|
+
response:
|
|
119
|
+
items_path: data
|
|
120
|
+
fields:
|
|
121
|
+
id: "{{ item.id | str }}"
|
|
122
|
+
author: "{{ item.user.login }}"
|
|
123
|
+
body: "{{ item.body }}"
|
|
124
|
+
created: "{{ item.created_at }}"
|
|
125
|
+
|
|
126
|
+
# --- Query ---
|
|
127
|
+
|
|
128
|
+
search:
|
|
129
|
+
tool: github_search_issues
|
|
130
|
+
args:
|
|
131
|
+
query: "{{ query }}"
|
|
132
|
+
per_page: "{{ limit }}"
|
|
133
|
+
response:
|
|
134
|
+
items_path: data.items
|
|
135
|
+
fields:
|
|
136
|
+
key: "{{ item.number | str }}"
|
|
137
|
+
summary: "{{ item.title }}"
|
|
138
|
+
status: "{{ item.state }}"
|
|
139
|
+
|
|
140
|
+
# --- Lifecycle ---
|
|
141
|
+
# health is handled by the adapter framework (pings the MCP bridge).
|
|
142
|
+
# No MCP tool needed — set to null.
|
|
143
|
+
health: null
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Pydantic models for declarative MCP adapter YAML config.
|
|
2
|
+
|
|
3
|
+
Validates the YAML structure used by DeclarativeMcpAdapter.
|
|
4
|
+
No ``model`` field in ResponseMapping — the adapter infers
|
|
5
|
+
the return type from the protocol method name (AR-R1).
|
|
6
|
+
|
|
7
|
+
Architecture: ADR-041, E337, E338 (AR-C2: shared ServerConnection)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Literal
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, Field, model_validator
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AdapterMeta(BaseModel):
|
|
18
|
+
"""Top-level adapter identity and protocol selection."""
|
|
19
|
+
|
|
20
|
+
name: str = Field(..., description="Adapter name (used in --adapter flag)")
|
|
21
|
+
protocol: Literal["pm", "docs"] = Field(
|
|
22
|
+
..., description="Protocol: 'pm' (ProjectManagement) or 'docs' (Documentation)"
|
|
23
|
+
)
|
|
24
|
+
description: str | None = Field(
|
|
25
|
+
default=None, description="Human-readable description"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ResponseMapping(BaseModel):
|
|
30
|
+
"""How to parse MCP tool response into adapter models.
|
|
31
|
+
|
|
32
|
+
No ``model`` field — adapter infers return type from method name (AR-R1).
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
fields: dict[str, str] = Field(
|
|
36
|
+
..., description="Field name → expression template mapping"
|
|
37
|
+
)
|
|
38
|
+
items_path: str | None = Field(
|
|
39
|
+
default=None,
|
|
40
|
+
description="Dot-path to list in response (e.g. 'data.items')",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MethodMapping(BaseModel):
|
|
45
|
+
"""Maps a protocol method to an MCP tool call."""
|
|
46
|
+
|
|
47
|
+
tool: str = Field(..., description="MCP tool name to call")
|
|
48
|
+
args: dict[str, str] = Field(
|
|
49
|
+
default_factory=dict,
|
|
50
|
+
description="Argument name → expression template mapping",
|
|
51
|
+
)
|
|
52
|
+
response: ResponseMapping | None = Field(
|
|
53
|
+
default=None, description="Response parsing config (None = raw result)"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ServerRef(BaseModel):
|
|
58
|
+
"""Server config: reference MCP registry OR inline connection.
|
|
59
|
+
|
|
60
|
+
Either ``ref`` (name in ``.raise/mcp/`` registry) or ``command``
|
|
61
|
+
(inline connection) must be provided. Both may be set but ref
|
|
62
|
+
takes precedence at resolution time.
|
|
63
|
+
|
|
64
|
+
Architecture: E338 S338.5 — Option B (separate from ServerConnection).
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
ref: str | None = Field(default=None, description="Name in .raise/mcp/ registry")
|
|
68
|
+
command: str | None = Field(
|
|
69
|
+
default=None, description="Server command (e.g. 'uvx', 'npx')"
|
|
70
|
+
)
|
|
71
|
+
args: list[str] = Field(
|
|
72
|
+
default_factory=list, description="Server command arguments"
|
|
73
|
+
)
|
|
74
|
+
env: list[str] | None = Field(
|
|
75
|
+
default=None,
|
|
76
|
+
description="Env var names to pass to server subprocess",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@model_validator(mode="after")
|
|
80
|
+
def _ref_or_command(self) -> ServerRef:
|
|
81
|
+
if self.ref is None and self.command is None:
|
|
82
|
+
msg = "Either 'ref' or 'command' must be provided"
|
|
83
|
+
raise ValueError(msg)
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class DeclarativeAdapterConfig(BaseModel):
|
|
88
|
+
"""Root config model for a declarative MCP adapter.
|
|
89
|
+
|
|
90
|
+
Parsed from ``.raise/adapters/<name>.yaml``.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
adapter: AdapterMeta
|
|
94
|
+
server: ServerRef
|
|
95
|
+
methods: dict[str, MethodMapping | None] = Field(
|
|
96
|
+
default_factory=dict,
|
|
97
|
+
description="Protocol method → MCP tool mapping. None = unsupported.",
|
|
98
|
+
)
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""Filesystem-based PM adapter with YAML file store.
|
|
2
|
+
|
|
3
|
+
Open-core fallback: provides read + write PM functionality without
|
|
4
|
+
external services. Each issue is a YAML file at
|
|
5
|
+
``.raise/backlog/items/{KEY}.yaml`` validated by Pydantic on load/dump.
|
|
6
|
+
|
|
7
|
+
Architecture: S347.2 (E347 Backlog Automation)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from datetime import UTC, datetime
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
import yaml
|
|
18
|
+
|
|
19
|
+
from raise_cli.adapters.models import (
|
|
20
|
+
AdapterHealth,
|
|
21
|
+
BacklogComment,
|
|
22
|
+
BacklogItem,
|
|
23
|
+
BacklogLink,
|
|
24
|
+
BatchResult,
|
|
25
|
+
Comment,
|
|
26
|
+
CommentRef,
|
|
27
|
+
FailureDetail,
|
|
28
|
+
IssueDetail,
|
|
29
|
+
IssueRef,
|
|
30
|
+
IssueSpec,
|
|
31
|
+
IssueSummary,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FilesystemPMAdapter:
|
|
36
|
+
"""PM adapter backed by YAML file store.
|
|
37
|
+
|
|
38
|
+
Each issue lives at ``.raise/backlog/items/{KEY}.yaml``.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, project_root: Path | None = None) -> None:
|
|
42
|
+
self._root = project_root or Path.cwd()
|
|
43
|
+
self._items_dir = self._root / ".raise" / "backlog" / "items"
|
|
44
|
+
|
|
45
|
+
# -- YAML I/O helpers ---------------------------------------------------
|
|
46
|
+
|
|
47
|
+
def _item_path(self, key: str) -> Path:
|
|
48
|
+
"""Path to a YAML item file."""
|
|
49
|
+
return self._items_dir / f"{key}.yaml"
|
|
50
|
+
|
|
51
|
+
def _load_item(self, key: str) -> BacklogItem:
|
|
52
|
+
"""Load and validate a single YAML item. Raises KeyError if missing."""
|
|
53
|
+
path = self._item_path(key)
|
|
54
|
+
if not path.exists():
|
|
55
|
+
raise KeyError(key)
|
|
56
|
+
raw = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
57
|
+
return BacklogItem.model_validate(raw)
|
|
58
|
+
|
|
59
|
+
def _save_item(self, item: BacklogItem) -> None:
|
|
60
|
+
"""Dump a BacklogItem to YAML, excluding None values for clean files."""
|
|
61
|
+
self._items_dir.mkdir(parents=True, exist_ok=True)
|
|
62
|
+
data = item.model_dump(exclude_none=True)
|
|
63
|
+
# Remove empty collections for clean YAML
|
|
64
|
+
for field in ("comments", "links", "labels"):
|
|
65
|
+
if field in data and not data[field]:
|
|
66
|
+
del data[field]
|
|
67
|
+
if "description" in data and not data["description"]:
|
|
68
|
+
del data["description"]
|
|
69
|
+
path = self._item_path(item.key)
|
|
70
|
+
path.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8")
|
|
71
|
+
|
|
72
|
+
def _load_all_items(self) -> list[BacklogItem]:
|
|
73
|
+
"""Load all YAML items from the store."""
|
|
74
|
+
if not self._items_dir.is_dir():
|
|
75
|
+
return []
|
|
76
|
+
items: list[BacklogItem] = []
|
|
77
|
+
for path in sorted(self._items_dir.glob("*.yaml")):
|
|
78
|
+
raw = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
79
|
+
items.append(BacklogItem.model_validate(raw))
|
|
80
|
+
return items
|
|
81
|
+
|
|
82
|
+
# -- Key generation helpers -----------------------------------------------
|
|
83
|
+
|
|
84
|
+
def _next_epic_key(self) -> str:
|
|
85
|
+
"""Scan existing E{N}.yaml files and return E{N+1}."""
|
|
86
|
+
max_n = 0
|
|
87
|
+
if self._items_dir.is_dir():
|
|
88
|
+
for path in self._items_dir.glob("E*.yaml"):
|
|
89
|
+
m = re.match(r"E(\d+)\.yaml$", path.name)
|
|
90
|
+
if m:
|
|
91
|
+
max_n = max(max_n, int(m.group(1)))
|
|
92
|
+
return f"E{max_n + 1}"
|
|
93
|
+
|
|
94
|
+
def _next_story_key(self, parent_key: str) -> str:
|
|
95
|
+
"""Scan existing S{epic_num}.{M}.yaml and return S{epic_num}.{M+1}.
|
|
96
|
+
|
|
97
|
+
Requires the parent epic key (e.g., 'E1') to derive the epic number.
|
|
98
|
+
"""
|
|
99
|
+
m = re.match(r"E(\d+)", parent_key)
|
|
100
|
+
if not m:
|
|
101
|
+
raise ValueError(f"Cannot derive epic number from parent key: {parent_key}")
|
|
102
|
+
epic_num = m.group(1)
|
|
103
|
+
max_m = 0
|
|
104
|
+
if self._items_dir.is_dir():
|
|
105
|
+
for path in self._items_dir.glob(f"S{epic_num}.*.yaml"):
|
|
106
|
+
sm = re.match(rf"S{epic_num}\.(\d+)\.yaml$", path.name)
|
|
107
|
+
if sm:
|
|
108
|
+
max_m = max(max_m, int(sm.group(1)))
|
|
109
|
+
return f"S{epic_num}.{max_m + 1}"
|
|
110
|
+
|
|
111
|
+
# -- Search helper -------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
def _match(self, item: IssueSummary, query: str) -> bool:
|
|
114
|
+
"""Check if an item matches a search query.
|
|
115
|
+
|
|
116
|
+
Supports:
|
|
117
|
+
- Empty query: matches all
|
|
118
|
+
- field = value: exact match on status, key
|
|
119
|
+
- bare text: case-insensitive substring match on key + summary
|
|
120
|
+
"""
|
|
121
|
+
query = query.strip()
|
|
122
|
+
if not query:
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
# field = value
|
|
126
|
+
m = re.match(r"(\w+)\s*=\s*(.+)", query)
|
|
127
|
+
if m:
|
|
128
|
+
field, value = m.group(1).lower(), m.group(2).strip().lower()
|
|
129
|
+
if field == "status":
|
|
130
|
+
return item.status.lower() == value
|
|
131
|
+
if field == "name":
|
|
132
|
+
return value in item.summary.lower()
|
|
133
|
+
if field == "priority":
|
|
134
|
+
return False
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
# bare text -> substring match on key + summary
|
|
138
|
+
q = query.lower()
|
|
139
|
+
return q in item.key.lower() or q in item.summary.lower()
|
|
140
|
+
|
|
141
|
+
# -- Read operations ----------------------------------------------------
|
|
142
|
+
|
|
143
|
+
def get_issue(self, key: str) -> IssueDetail:
|
|
144
|
+
"""Get issue detail by key."""
|
|
145
|
+
item = self._load_item(key)
|
|
146
|
+
return IssueDetail(
|
|
147
|
+
key=item.key,
|
|
148
|
+
summary=item.summary,
|
|
149
|
+
status=item.status,
|
|
150
|
+
issue_type=item.issue_type,
|
|
151
|
+
description=item.description,
|
|
152
|
+
labels=item.labels,
|
|
153
|
+
parent_key=item.parent,
|
|
154
|
+
priority=item.priority,
|
|
155
|
+
assignee=item.assignee,
|
|
156
|
+
created=item.created,
|
|
157
|
+
updated=item.updated,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def search(self, query: str, limit: int = 50) -> list[IssueSummary]:
|
|
161
|
+
"""Search issues."""
|
|
162
|
+
items = self._load_all_items()
|
|
163
|
+
summaries = [
|
|
164
|
+
IssueSummary(
|
|
165
|
+
key=it.key,
|
|
166
|
+
summary=it.summary,
|
|
167
|
+
status=it.status,
|
|
168
|
+
issue_type=it.issue_type,
|
|
169
|
+
parent_key=it.parent,
|
|
170
|
+
)
|
|
171
|
+
for it in items
|
|
172
|
+
]
|
|
173
|
+
matched = [s for s in summaries if self._match(s, query)]
|
|
174
|
+
return matched[:limit]
|
|
175
|
+
|
|
176
|
+
def get_comments(self, key: str, limit: int = 10) -> list[Comment]:
|
|
177
|
+
"""Get comments for an issue. Returns [] if issue not found."""
|
|
178
|
+
try:
|
|
179
|
+
item = self._load_item(key)
|
|
180
|
+
except KeyError:
|
|
181
|
+
return []
|
|
182
|
+
comments = [
|
|
183
|
+
Comment(id=c.id, body=c.body, author=c.author, created=c.created)
|
|
184
|
+
for c in item.comments
|
|
185
|
+
]
|
|
186
|
+
return comments[:limit]
|
|
187
|
+
|
|
188
|
+
def health(self) -> AdapterHealth:
|
|
189
|
+
"""Check adapter health."""
|
|
190
|
+
if self._items_dir.is_dir():
|
|
191
|
+
count = len(list(self._items_dir.glob("*.yaml")))
|
|
192
|
+
return AdapterHealth(
|
|
193
|
+
name="filesystem",
|
|
194
|
+
healthy=True,
|
|
195
|
+
message=f".raise/backlog/items/ ({count} items)",
|
|
196
|
+
)
|
|
197
|
+
return AdapterHealth(
|
|
198
|
+
name="filesystem",
|
|
199
|
+
healthy=False,
|
|
200
|
+
message="YAML store not found (.raise/backlog/items/)",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# -- Write operations ---------------------------------------------------
|
|
204
|
+
|
|
205
|
+
def create_issue(self, project_key: str, issue: IssueSpec) -> IssueRef:
|
|
206
|
+
"""Create a new issue."""
|
|
207
|
+
meta = issue.metadata or {}
|
|
208
|
+
now = datetime.now(UTC).isoformat()
|
|
209
|
+
itype = issue.issue_type.lower()
|
|
210
|
+
if itype == "epic":
|
|
211
|
+
key = self._next_epic_key()
|
|
212
|
+
elif itype in ("story", "subtask"):
|
|
213
|
+
parent_key = meta.get("parent_key")
|
|
214
|
+
if not parent_key:
|
|
215
|
+
raise KeyError("Story creation requires parent_key")
|
|
216
|
+
key = self._next_story_key(parent_key)
|
|
217
|
+
else:
|
|
218
|
+
# Default to epic-style key for Task and other types
|
|
219
|
+
key = self._next_epic_key()
|
|
220
|
+
new_item = BacklogItem(
|
|
221
|
+
key=key,
|
|
222
|
+
summary=issue.summary,
|
|
223
|
+
issue_type=issue.issue_type,
|
|
224
|
+
status="pending",
|
|
225
|
+
parent=meta.get("parent_key"),
|
|
226
|
+
description=issue.description,
|
|
227
|
+
labels=issue.labels,
|
|
228
|
+
priority=meta.get("priority"),
|
|
229
|
+
created=now,
|
|
230
|
+
updated=now,
|
|
231
|
+
)
|
|
232
|
+
self._save_item(new_item)
|
|
233
|
+
return IssueRef(key=new_item.key)
|
|
234
|
+
|
|
235
|
+
# Fields that must not be mutated via update_issue()
|
|
236
|
+
_IMMUTABLE_FIELDS: frozenset[str] = frozenset(
|
|
237
|
+
{"key", "created", "comments", "links"}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def update_issue(self, key: str, fields: dict[str, Any]) -> IssueRef:
|
|
241
|
+
"""Update fields on an existing issue."""
|
|
242
|
+
item = self._load_item(key)
|
|
243
|
+
for field_name, value in fields.items():
|
|
244
|
+
if field_name in self._IMMUTABLE_FIELDS:
|
|
245
|
+
continue
|
|
246
|
+
if hasattr(item, field_name):
|
|
247
|
+
setattr(item, field_name, value)
|
|
248
|
+
item.updated = datetime.now(UTC).isoformat()
|
|
249
|
+
self._save_item(item)
|
|
250
|
+
return IssueRef(key=key)
|
|
251
|
+
|
|
252
|
+
def transition_issue(self, key: str, status: str) -> IssueRef:
|
|
253
|
+
"""Update issue status."""
|
|
254
|
+
item = self._load_item(key)
|
|
255
|
+
item.status = status
|
|
256
|
+
item.updated = datetime.now(UTC).isoformat()
|
|
257
|
+
self._save_item(item)
|
|
258
|
+
return IssueRef(key=key)
|
|
259
|
+
|
|
260
|
+
def batch_transition(self, keys: list[str], status: str) -> BatchResult:
|
|
261
|
+
"""Transition multiple issues."""
|
|
262
|
+
succeeded: list[IssueRef] = []
|
|
263
|
+
failed: list[FailureDetail] = []
|
|
264
|
+
for key in keys:
|
|
265
|
+
try:
|
|
266
|
+
ref = self.transition_issue(key, status)
|
|
267
|
+
succeeded.append(ref)
|
|
268
|
+
except KeyError:
|
|
269
|
+
failed.append(FailureDetail(key=key, error=f"{key} not found"))
|
|
270
|
+
return BatchResult(succeeded=succeeded, failed=failed)
|
|
271
|
+
|
|
272
|
+
# -- Relationship & comment operations ----------------------------------
|
|
273
|
+
|
|
274
|
+
def link_to_parent(self, child_key: str, parent_key: str) -> None:
|
|
275
|
+
"""Set parent field on child issue."""
|
|
276
|
+
item = self._load_item(child_key)
|
|
277
|
+
item.parent = parent_key
|
|
278
|
+
item.updated = datetime.now(UTC).isoformat()
|
|
279
|
+
self._save_item(item)
|
|
280
|
+
|
|
281
|
+
def link_issues(self, source: str, target: str, link_type: str) -> None:
|
|
282
|
+
"""Add a link from source to target."""
|
|
283
|
+
item = self._load_item(source)
|
|
284
|
+
item.links.append(BacklogLink(target=target, link_type=link_type))
|
|
285
|
+
item.updated = datetime.now(UTC).isoformat()
|
|
286
|
+
self._save_item(item)
|
|
287
|
+
|
|
288
|
+
def add_comment(self, key: str, body: str) -> CommentRef:
|
|
289
|
+
"""Add a comment to an issue."""
|
|
290
|
+
item = self._load_item(key)
|
|
291
|
+
next_n = len(item.comments) + 1
|
|
292
|
+
comment_id = f"{key}-{next_n}"
|
|
293
|
+
now = datetime.now(UTC).isoformat()
|
|
294
|
+
item.comments.append(
|
|
295
|
+
BacklogComment(id=comment_id, body=body, author="rai", created=now)
|
|
296
|
+
)
|
|
297
|
+
item.updated = now
|
|
298
|
+
self._save_item(item)
|
|
299
|
+
return CommentRef(id=comment_id)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Backwards-compat re-export. Import from raise_cli.mcp.bridge instead.
|
|
2
|
+
|
|
3
|
+
Architecture: ADR-042, E338 — McpBridge moved to raise_cli.mcp.bridge.
|
|
4
|
+
This shim exists for backwards compatibility with external consumers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from raise_cli.mcp.bridge import McpBridge, McpBridgeError # noqa: F401
|
|
8
|
+
from raise_cli.mcp.models import McpToolInfo, McpToolResult # noqa: F401
|
|
9
|
+
|
|
10
|
+
__all__ = ["McpBridge", "McpBridgeError", "McpToolInfo", "McpToolResult"]
|