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,201 @@
|
|
|
1
|
+
"""Project manifest schema and persistence.
|
|
2
|
+
|
|
3
|
+
The manifest file (.raise/manifest.yaml) stores project metadata detected
|
|
4
|
+
during initialization, including project type and code file count.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from datetime import UTC, datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, cast
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
from pydantic import BaseModel, Field, ValidationError, model_validator
|
|
16
|
+
|
|
17
|
+
from raise_cli.config.ide import IdeType
|
|
18
|
+
from raise_cli.config.paths import MANIFEST_FILE, get_raise_dir
|
|
19
|
+
from raise_cli.onboarding.detection import ProjectType
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ProjectInfo(BaseModel):
|
|
25
|
+
"""Information about the project detected during init.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
name: Project name (usually directory name).
|
|
29
|
+
project_type: Whether greenfield or brownfield.
|
|
30
|
+
language: Dominant programming language (auto-detected or user-specified).
|
|
31
|
+
test_command: Command to run tests (configuration over convention).
|
|
32
|
+
lint_command: Command to run linter (configuration over convention).
|
|
33
|
+
type_check_command: Command to run type checker (configuration over convention).
|
|
34
|
+
code_file_count: Number of code files detected.
|
|
35
|
+
detected_at: When the project was initialized.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
name: str
|
|
39
|
+
project_type: ProjectType
|
|
40
|
+
language: str | None = None
|
|
41
|
+
test_command: str | None = None
|
|
42
|
+
lint_command: str | None = None
|
|
43
|
+
type_check_command: str | None = None
|
|
44
|
+
code_file_count: int = 0
|
|
45
|
+
detected_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BranchConfig(BaseModel):
|
|
49
|
+
"""Branch naming configuration for the project.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
development: The development/integration branch name.
|
|
53
|
+
main: The stable/production branch name.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
development: str = "main"
|
|
57
|
+
main: str = "main"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class IdeManifest(BaseModel):
|
|
61
|
+
"""IDE configuration persisted in manifest (legacy single-IDE format).
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
type: Which IDE this project uses.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
type: IdeType = "claude"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class AgentsManifest(BaseModel):
|
|
71
|
+
"""Multi-agent configuration persisted in manifest.
|
|
72
|
+
|
|
73
|
+
Replaces IdeManifest with a list to support multiple simultaneous agents.
|
|
74
|
+
|
|
75
|
+
Attributes:
|
|
76
|
+
types: List of active agent types (e.g. ["claude", "cursor", "windsurf"]).
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
types: list[str] = Field(default_factory=lambda: ["claude"])
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class BacklogConfig(BaseModel):
|
|
83
|
+
"""Backlog configuration from manifest (optional section).
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
adapter_default: Default PM adapter name (e.g., 'jira', 'filesystem').
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
adapter_default: str | None = None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TierConfig(BaseModel):
|
|
93
|
+
"""Tier configuration from manifest (optional section).
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
level: Tier level string (community, pro, enterprise).
|
|
97
|
+
backend_url: Backend URL for PRO/Enterprise tiers.
|
|
98
|
+
capabilities: List of capability strings enabled for this tier.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
level: str = "community"
|
|
102
|
+
backend_url: str | None = None
|
|
103
|
+
capabilities: list[str] = Field(default_factory=list)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ProjectManifest(BaseModel):
|
|
107
|
+
"""Project manifest stored in .raise/manifest.yaml.
|
|
108
|
+
|
|
109
|
+
Attributes:
|
|
110
|
+
version: Manifest schema version.
|
|
111
|
+
project: Project information.
|
|
112
|
+
branches: Branch naming configuration.
|
|
113
|
+
ide: Legacy single-IDE configuration (backward compat — read/write).
|
|
114
|
+
agents: Multi-agent configuration (new format).
|
|
115
|
+
tier: Optional tier configuration (S211.5).
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
version: str = "1.0"
|
|
119
|
+
project: ProjectInfo
|
|
120
|
+
branches: BranchConfig = Field(default_factory=BranchConfig)
|
|
121
|
+
ide: IdeManifest = Field(default_factory=IdeManifest)
|
|
122
|
+
agents: AgentsManifest = Field(default_factory=AgentsManifest)
|
|
123
|
+
tier: TierConfig | None = None
|
|
124
|
+
backlog: BacklogConfig | None = None
|
|
125
|
+
|
|
126
|
+
@model_validator(mode="before")
|
|
127
|
+
@classmethod
|
|
128
|
+
def _migrate_ide_to_agents(cls, data: Any) -> dict[str, Any]:
|
|
129
|
+
"""Migrate old ide.type format to agents.types on load.
|
|
130
|
+
|
|
131
|
+
If 'agents' key is absent but 'ide' key is present, derive
|
|
132
|
+
agents.types from ide.type for backward compat.
|
|
133
|
+
"""
|
|
134
|
+
if not isinstance(data, dict):
|
|
135
|
+
return cast(dict[str, Any], data)
|
|
136
|
+
typed: dict[str, Any] = cast(dict[str, Any], data)
|
|
137
|
+
if "agents" not in typed and "ide" in typed:
|
|
138
|
+
raw_ide: object = typed["ide"]
|
|
139
|
+
if isinstance(raw_ide, dict):
|
|
140
|
+
raw_type: object = cast(dict[str, object], raw_ide).get(
|
|
141
|
+
"type", "claude"
|
|
142
|
+
)
|
|
143
|
+
ide_type: str = str(raw_type) if raw_type is not None else "claude"
|
|
144
|
+
else:
|
|
145
|
+
ide_type = "claude"
|
|
146
|
+
typed["agents"] = {"types": [ide_type]}
|
|
147
|
+
return typed
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def save_manifest(manifest: ProjectManifest, project_root: Path) -> None:
|
|
151
|
+
"""Save project manifest to .raise/manifest.yaml.
|
|
152
|
+
|
|
153
|
+
Creates .raise/ directory if it doesn't exist.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
manifest: The manifest to save.
|
|
157
|
+
project_root: Root directory of the project.
|
|
158
|
+
"""
|
|
159
|
+
raise_dir = get_raise_dir(project_root)
|
|
160
|
+
raise_dir.mkdir(parents=True, exist_ok=True)
|
|
161
|
+
|
|
162
|
+
manifest_path = raise_dir / MANIFEST_FILE
|
|
163
|
+
|
|
164
|
+
# Convert to dict with proper serialization
|
|
165
|
+
data = manifest.model_dump(mode="json")
|
|
166
|
+
|
|
167
|
+
content = yaml.dump(
|
|
168
|
+
data, default_flow_style=False, allow_unicode=True, sort_keys=False
|
|
169
|
+
)
|
|
170
|
+
manifest_path.write_text(content, encoding="utf-8")
|
|
171
|
+
logger.debug("Saved manifest: %s", manifest_path)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def load_manifest(project_root: Path) -> ProjectManifest | None:
|
|
175
|
+
"""Load project manifest from .raise/manifest.yaml.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
project_root: Root directory of the project.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
ProjectManifest if file exists and is valid, None otherwise.
|
|
182
|
+
"""
|
|
183
|
+
manifest_path = get_raise_dir(project_root) / MANIFEST_FILE
|
|
184
|
+
|
|
185
|
+
if not manifest_path.exists():
|
|
186
|
+
logger.debug("Manifest not found: %s", manifest_path)
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
content = manifest_path.read_text(encoding="utf-8")
|
|
191
|
+
data = yaml.safe_load(content)
|
|
192
|
+
if data is None:
|
|
193
|
+
logger.warning("Empty manifest: %s", manifest_path)
|
|
194
|
+
return None
|
|
195
|
+
return ProjectManifest.model_validate(data)
|
|
196
|
+
except yaml.YAMLError as e:
|
|
197
|
+
logger.warning("Invalid YAML in manifest: %s", e)
|
|
198
|
+
return None
|
|
199
|
+
except ValidationError as e:
|
|
200
|
+
logger.warning("Invalid manifest schema: %s", e)
|
|
201
|
+
return None
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"""MEMORY.md generation for Claude Code and other AI editors.
|
|
2
|
+
|
|
3
|
+
Agent-agnostic generator: produces markdown string from methodology.yaml
|
|
4
|
+
and patterns.jsonl. Knows nothing about file paths or write destinations.
|
|
5
|
+
|
|
6
|
+
The generator is IDE-independent — placement functions (in CLI commands)
|
|
7
|
+
decide where to write the output.
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
from raise_cli.onboarding.memory_md import MemoryMdGenerator
|
|
11
|
+
|
|
12
|
+
gen = MemoryMdGenerator()
|
|
13
|
+
content = gen.generate(
|
|
14
|
+
methodology_path=Path(".raise/rai/framework/methodology.yaml"),
|
|
15
|
+
patterns_path=Path(".raise/rai/memory/patterns.jsonl"),
|
|
16
|
+
project_name="my-project",
|
|
17
|
+
)
|
|
18
|
+
Path("MEMORY.md").write_text(content)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import logging
|
|
25
|
+
from datetime import date
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
import yaml
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MemoryMdGenerator:
|
|
35
|
+
"""Generates MEMORY.md content from methodology and patterns.
|
|
36
|
+
|
|
37
|
+
Part 1 (static): Lifecycle, skills, gates, principles, branches
|
|
38
|
+
from methodology.yaml.
|
|
39
|
+
|
|
40
|
+
Part 2 (dynamic): Key patterns from patterns.jsonl.
|
|
41
|
+
|
|
42
|
+
The generator returns a string — it never writes to disk.
|
|
43
|
+
This separation enables multi-IDE support: generate once,
|
|
44
|
+
write to Claude Code, Cursor, Windsurf, etc.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def generate(
|
|
48
|
+
self,
|
|
49
|
+
methodology_path: Path | None = None,
|
|
50
|
+
patterns_path: Path | None = None,
|
|
51
|
+
project_name: str = "project",
|
|
52
|
+
max_patterns: int = 10,
|
|
53
|
+
development_branch: str = "main",
|
|
54
|
+
) -> str:
|
|
55
|
+
"""Generate MEMORY.md content.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
methodology_path: Path to methodology.yaml (Part 1 source).
|
|
59
|
+
patterns_path: Path to patterns.jsonl (Part 2 source).
|
|
60
|
+
project_name: Project name for the header.
|
|
61
|
+
max_patterns: Maximum patterns to include in Part 2.
|
|
62
|
+
development_branch: Development branch name for placeholder
|
|
63
|
+
substitution in branch model (default: "main").
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Markdown content for MEMORY.md.
|
|
67
|
+
"""
|
|
68
|
+
lines: list[str] = []
|
|
69
|
+
|
|
70
|
+
self._add_header(lines, project_name)
|
|
71
|
+
|
|
72
|
+
# Part 1: Static methodology
|
|
73
|
+
methodology = self._load_methodology(methodology_path)
|
|
74
|
+
if methodology:
|
|
75
|
+
self._add_lifecycle_section(lines, methodology)
|
|
76
|
+
self._add_skills_section(lines, methodology)
|
|
77
|
+
self._add_gates_section(lines, methodology)
|
|
78
|
+
self._add_principles_section(lines, methodology)
|
|
79
|
+
self._add_branches_section(lines, methodology, development_branch)
|
|
80
|
+
|
|
81
|
+
# Part 2: Dynamic patterns
|
|
82
|
+
patterns = self._load_patterns(patterns_path)
|
|
83
|
+
self._add_patterns_section(lines, patterns, max_patterns)
|
|
84
|
+
|
|
85
|
+
self._add_footer(lines)
|
|
86
|
+
|
|
87
|
+
return "\n".join(lines)
|
|
88
|
+
|
|
89
|
+
# =========================================================================
|
|
90
|
+
# Header & Footer
|
|
91
|
+
# =========================================================================
|
|
92
|
+
|
|
93
|
+
def _add_header(self, lines: list[str], project_name: str) -> None:
|
|
94
|
+
"""Add document header."""
|
|
95
|
+
lines.append(f"# Rai Memory — {project_name}")
|
|
96
|
+
lines.append("")
|
|
97
|
+
lines.append(
|
|
98
|
+
"> Permanent knowledge for this project. Loaded into system prompt."
|
|
99
|
+
)
|
|
100
|
+
lines.append("")
|
|
101
|
+
lines.append("---")
|
|
102
|
+
lines.append("")
|
|
103
|
+
|
|
104
|
+
def _add_footer(self, lines: list[str]) -> None:
|
|
105
|
+
"""Add document footer with timestamp."""
|
|
106
|
+
lines.append(f"*Last updated: {date.today().isoformat()}*")
|
|
107
|
+
lines.append("*Generated by `rai graph build`*")
|
|
108
|
+
lines.append("")
|
|
109
|
+
|
|
110
|
+
# =========================================================================
|
|
111
|
+
# Part 1: Methodology sections
|
|
112
|
+
# =========================================================================
|
|
113
|
+
|
|
114
|
+
def _add_lifecycle_section(
|
|
115
|
+
self, lines: list[str], methodology: dict[str, Any]
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Add lifecycle flows section."""
|
|
118
|
+
lifecycle = methodology.get("lifecycle", {})
|
|
119
|
+
if not lifecycle:
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
lines.append("## RaiSE Framework Process")
|
|
123
|
+
lines.append("")
|
|
124
|
+
lines.append("### Work Lifecycle (Always Follow)")
|
|
125
|
+
lines.append("")
|
|
126
|
+
lines.append("```")
|
|
127
|
+
|
|
128
|
+
for level_name, level_data in lifecycle.items():
|
|
129
|
+
label = level_name.upper() + " LEVEL"
|
|
130
|
+
if level_name == "session":
|
|
131
|
+
label = "SESSION LEVEL"
|
|
132
|
+
elif level_name == "story":
|
|
133
|
+
label = "STORY LEVEL (per story)"
|
|
134
|
+
elif level_name == "epic":
|
|
135
|
+
label = "EPIC LEVEL"
|
|
136
|
+
|
|
137
|
+
lines.append(f"{label}:")
|
|
138
|
+
lines.append(f" {level_data['flow']}")
|
|
139
|
+
lines.append("")
|
|
140
|
+
|
|
141
|
+
# Add note if present
|
|
142
|
+
if "note" in level_data:
|
|
143
|
+
lines.append(f"* {level_data['note']}")
|
|
144
|
+
lines.append("")
|
|
145
|
+
|
|
146
|
+
# Remove trailing blank line inside code block
|
|
147
|
+
if lines and lines[-1] == "":
|
|
148
|
+
lines.pop()
|
|
149
|
+
lines.append("```")
|
|
150
|
+
lines.append("")
|
|
151
|
+
|
|
152
|
+
def _add_skills_section(
|
|
153
|
+
self, lines: list[str], methodology: dict[str, Any]
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Add skills by category."""
|
|
156
|
+
skills = methodology.get("skills", {})
|
|
157
|
+
if not skills:
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
# Count total skills
|
|
161
|
+
total = sum(len(category_skills) for category_skills in skills.values())
|
|
162
|
+
|
|
163
|
+
lines.append(f"## Available Skills ({total} total)")
|
|
164
|
+
lines.append("")
|
|
165
|
+
|
|
166
|
+
# Category display names
|
|
167
|
+
category_names = {
|
|
168
|
+
"session": "Session Skills",
|
|
169
|
+
"epic": "Epic Skills",
|
|
170
|
+
"story": "Story Skills",
|
|
171
|
+
"discovery": "Discovery Skills",
|
|
172
|
+
"meta": "Meta Skills",
|
|
173
|
+
"other": "Other Skills",
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for category, category_skills in skills.items():
|
|
177
|
+
display_name = category_names.get(category, f"{category.title()} Skills")
|
|
178
|
+
lines.append(f"### {display_name}")
|
|
179
|
+
|
|
180
|
+
for skill in category_skills:
|
|
181
|
+
name = skill["name"]
|
|
182
|
+
purpose = skill["purpose"]
|
|
183
|
+
lines.append(f"- `{name}` — {purpose}")
|
|
184
|
+
|
|
185
|
+
lines.append("")
|
|
186
|
+
|
|
187
|
+
lines.append("---")
|
|
188
|
+
lines.append("")
|
|
189
|
+
|
|
190
|
+
def _add_gates_section(self, lines: list[str], methodology: dict[str, Any]) -> None:
|
|
191
|
+
"""Add gate requirements as table."""
|
|
192
|
+
gates = methodology.get("gates", {})
|
|
193
|
+
blocking = gates.get("blocking", [])
|
|
194
|
+
if not blocking:
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
lines.append("### Gate Requirements")
|
|
198
|
+
lines.append("")
|
|
199
|
+
lines.append("| Gate | Required Before |")
|
|
200
|
+
lines.append("|------|-----------------|")
|
|
201
|
+
|
|
202
|
+
for gate in blocking:
|
|
203
|
+
require = gate["require"]
|
|
204
|
+
before = gate["before"]
|
|
205
|
+
enforced_by = gate.get("enforced_by", "")
|
|
206
|
+
enforced_str = f" ({enforced_by})" if enforced_by else ""
|
|
207
|
+
lines.append(f"| **{require}** | **{before}**{enforced_str} |")
|
|
208
|
+
|
|
209
|
+
# Add quality gates as simple rows
|
|
210
|
+
quality = gates.get("quality", [])
|
|
211
|
+
for gate in quality:
|
|
212
|
+
gate_name = gate["gate"]
|
|
213
|
+
when = gate["when"]
|
|
214
|
+
lines.append(f"| {gate_name} | {when} |")
|
|
215
|
+
|
|
216
|
+
lines.append("")
|
|
217
|
+
lines.append("---")
|
|
218
|
+
lines.append("")
|
|
219
|
+
|
|
220
|
+
def _add_principles_section(
|
|
221
|
+
self, lines: list[str], methodology: dict[str, Any]
|
|
222
|
+
) -> None:
|
|
223
|
+
"""Add principles as numbered rules."""
|
|
224
|
+
principles = methodology.get("principles", {})
|
|
225
|
+
if not principles:
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
lines.append("## Critical Process Rules")
|
|
229
|
+
lines.append("")
|
|
230
|
+
|
|
231
|
+
rule_num = 1
|
|
232
|
+
for _category, category_principles in principles.items():
|
|
233
|
+
for principle in category_principles:
|
|
234
|
+
name = principle["name"]
|
|
235
|
+
rule = principle["rule"]
|
|
236
|
+
lines.append(f"{rule_num}. **{name}** — {rule}")
|
|
237
|
+
rule_num += 1
|
|
238
|
+
|
|
239
|
+
lines.append("")
|
|
240
|
+
lines.append("---")
|
|
241
|
+
lines.append("")
|
|
242
|
+
|
|
243
|
+
def _add_branches_section(
|
|
244
|
+
self,
|
|
245
|
+
lines: list[str],
|
|
246
|
+
methodology: dict[str, Any],
|
|
247
|
+
development_branch: str = "main",
|
|
248
|
+
) -> None:
|
|
249
|
+
"""Add branch model section."""
|
|
250
|
+
branches = methodology.get("branches", {})
|
|
251
|
+
if not branches:
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
lines.append("## Branch Model")
|
|
255
|
+
lines.append("")
|
|
256
|
+
|
|
257
|
+
structure = branches.get("structure", "")
|
|
258
|
+
if structure:
|
|
259
|
+
structure = structure.replace("{development_branch}", development_branch)
|
|
260
|
+
lines.append("```")
|
|
261
|
+
# structure may have trailing newline from YAML block scalar
|
|
262
|
+
for line in structure.rstrip().split("\n"):
|
|
263
|
+
lines.append(line)
|
|
264
|
+
lines.append("```")
|
|
265
|
+
lines.append("")
|
|
266
|
+
|
|
267
|
+
flow = branches.get("flow", [])
|
|
268
|
+
for item in flow:
|
|
269
|
+
item = item.replace("{development_branch}", development_branch)
|
|
270
|
+
lines.append(f"- {item}")
|
|
271
|
+
|
|
272
|
+
lines.append("")
|
|
273
|
+
lines.append("---")
|
|
274
|
+
lines.append("")
|
|
275
|
+
|
|
276
|
+
# =========================================================================
|
|
277
|
+
# Part 2: Patterns
|
|
278
|
+
# =========================================================================
|
|
279
|
+
|
|
280
|
+
def _add_patterns_section(
|
|
281
|
+
self,
|
|
282
|
+
lines: list[str],
|
|
283
|
+
patterns: list[dict[str, Any]],
|
|
284
|
+
max_patterns: int,
|
|
285
|
+
) -> None:
|
|
286
|
+
"""Add key patterns from patterns.jsonl."""
|
|
287
|
+
lines.append("## Key Patterns (from memory)")
|
|
288
|
+
lines.append("")
|
|
289
|
+
|
|
290
|
+
if not patterns:
|
|
291
|
+
lines.append("*No patterns yet. Patterns are learned during work.*")
|
|
292
|
+
lines.append("")
|
|
293
|
+
lines.append("---")
|
|
294
|
+
lines.append("")
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
# Take most recent patterns (already sorted by load order = chronological)
|
|
298
|
+
selected = patterns[-max_patterns:]
|
|
299
|
+
|
|
300
|
+
for pattern in selected:
|
|
301
|
+
pat_id = pattern.get("id", "?")
|
|
302
|
+
content = pattern.get("content") or pattern.get("pattern", "")
|
|
303
|
+
lines.append(f"- **{pat_id}:** {content}")
|
|
304
|
+
|
|
305
|
+
lines.append("")
|
|
306
|
+
lines.append("---")
|
|
307
|
+
lines.append("")
|
|
308
|
+
|
|
309
|
+
# =========================================================================
|
|
310
|
+
# Loaders
|
|
311
|
+
# =========================================================================
|
|
312
|
+
|
|
313
|
+
def _load_methodology(self, path: Path | None) -> dict[str, Any] | None:
|
|
314
|
+
"""Load and parse methodology.yaml.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
path: Path to methodology.yaml.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Parsed YAML dictionary, or None if unavailable.
|
|
321
|
+
"""
|
|
322
|
+
if path is None or not path.exists():
|
|
323
|
+
logger.debug("methodology.yaml not found at %s", path)
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
content = path.read_text(encoding="utf-8")
|
|
328
|
+
data: dict[str, Any] = yaml.safe_load(content)
|
|
329
|
+
return data
|
|
330
|
+
except (yaml.YAMLError, OSError) as e:
|
|
331
|
+
logger.warning("Failed to parse methodology.yaml: %s", e)
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
def _load_patterns(self, path: Path | None) -> list[dict[str, Any]]:
|
|
335
|
+
"""Load patterns from JSONL file.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
path: Path to patterns.jsonl.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
List of pattern dictionaries. Empty list if unavailable.
|
|
342
|
+
"""
|
|
343
|
+
if path is None or not path.exists():
|
|
344
|
+
return []
|
|
345
|
+
|
|
346
|
+
patterns: list[dict[str, Any]] = []
|
|
347
|
+
try:
|
|
348
|
+
with path.open("r", encoding="utf-8") as f:
|
|
349
|
+
for line in f:
|
|
350
|
+
line = line.strip()
|
|
351
|
+
if not line:
|
|
352
|
+
continue
|
|
353
|
+
try:
|
|
354
|
+
data = json.loads(line)
|
|
355
|
+
patterns.append(data)
|
|
356
|
+
except json.JSONDecodeError:
|
|
357
|
+
continue
|
|
358
|
+
except OSError as e:
|
|
359
|
+
logger.warning("Failed to read patterns.jsonl: %s", e)
|
|
360
|
+
|
|
361
|
+
return patterns
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def generate_memory_md(
|
|
365
|
+
methodology_path: Path | None = None,
|
|
366
|
+
patterns_path: Path | None = None,
|
|
367
|
+
project_name: str = "project",
|
|
368
|
+
max_patterns: int = 10,
|
|
369
|
+
development_branch: str = "main",
|
|
370
|
+
) -> str:
|
|
371
|
+
"""Convenience function to generate MEMORY.md content.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
methodology_path: Path to methodology.yaml.
|
|
375
|
+
patterns_path: Path to patterns.jsonl.
|
|
376
|
+
project_name: Project name for header.
|
|
377
|
+
max_patterns: Maximum patterns to include.
|
|
378
|
+
development_branch: Development branch name for placeholder
|
|
379
|
+
substitution in branch model (default: "main").
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
Markdown content for MEMORY.md.
|
|
383
|
+
|
|
384
|
+
Example:
|
|
385
|
+
>>> content = generate_memory_md(
|
|
386
|
+
... methodology_path=Path(".raise/rai/framework/methodology.yaml"),
|
|
387
|
+
... patterns_path=Path(".raise/rai/memory/patterns.jsonl"),
|
|
388
|
+
... project_name="my-api",
|
|
389
|
+
... )
|
|
390
|
+
>>> Path("MEMORY.md").write_text(content)
|
|
391
|
+
"""
|
|
392
|
+
generator = MemoryMdGenerator()
|
|
393
|
+
return generator.generate(
|
|
394
|
+
methodology_path=methodology_path,
|
|
395
|
+
patterns_path=patterns_path,
|
|
396
|
+
project_name=project_name,
|
|
397
|
+
max_patterns=max_patterns,
|
|
398
|
+
development_branch=development_branch,
|
|
399
|
+
)
|