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.
Files changed (264) hide show
  1. raise_cli/__init__.py +38 -0
  2. raise_cli/__main__.py +30 -0
  3. raise_cli/adapters/__init__.py +91 -0
  4. raise_cli/adapters/declarative/__init__.py +26 -0
  5. raise_cli/adapters/declarative/adapter.py +267 -0
  6. raise_cli/adapters/declarative/discovery.py +94 -0
  7. raise_cli/adapters/declarative/expressions.py +150 -0
  8. raise_cli/adapters/declarative/reference/__init__.py +1 -0
  9. raise_cli/adapters/declarative/reference/github.yaml +143 -0
  10. raise_cli/adapters/declarative/schema.py +98 -0
  11. raise_cli/adapters/filesystem.py +299 -0
  12. raise_cli/adapters/mcp_bridge.py +10 -0
  13. raise_cli/adapters/mcp_confluence.py +246 -0
  14. raise_cli/adapters/mcp_jira.py +405 -0
  15. raise_cli/adapters/models.py +205 -0
  16. raise_cli/adapters/protocols.py +180 -0
  17. raise_cli/adapters/registry.py +90 -0
  18. raise_cli/adapters/sync.py +149 -0
  19. raise_cli/agents/__init__.py +14 -0
  20. raise_cli/agents/antigravity.yaml +8 -0
  21. raise_cli/agents/claude.yaml +8 -0
  22. raise_cli/agents/copilot.yaml +8 -0
  23. raise_cli/agents/copilot_plugin.py +124 -0
  24. raise_cli/agents/cursor.yaml +7 -0
  25. raise_cli/agents/roo.yaml +8 -0
  26. raise_cli/agents/windsurf.yaml +8 -0
  27. raise_cli/artifacts/__init__.py +30 -0
  28. raise_cli/artifacts/models.py +43 -0
  29. raise_cli/artifacts/reader.py +55 -0
  30. raise_cli/artifacts/renderer.py +104 -0
  31. raise_cli/artifacts/story_design.py +69 -0
  32. raise_cli/artifacts/writer.py +45 -0
  33. raise_cli/backlog/__init__.py +1 -0
  34. raise_cli/backlog/sync.py +115 -0
  35. raise_cli/cli/__init__.py +3 -0
  36. raise_cli/cli/commands/__init__.py +3 -0
  37. raise_cli/cli/commands/_resolve.py +153 -0
  38. raise_cli/cli/commands/adapters.py +362 -0
  39. raise_cli/cli/commands/artifact.py +137 -0
  40. raise_cli/cli/commands/backlog.py +333 -0
  41. raise_cli/cli/commands/base.py +31 -0
  42. raise_cli/cli/commands/discover.py +551 -0
  43. raise_cli/cli/commands/docs.py +130 -0
  44. raise_cli/cli/commands/doctor.py +177 -0
  45. raise_cli/cli/commands/gate.py +223 -0
  46. raise_cli/cli/commands/graph.py +1086 -0
  47. raise_cli/cli/commands/info.py +81 -0
  48. raise_cli/cli/commands/init.py +746 -0
  49. raise_cli/cli/commands/journal.py +167 -0
  50. raise_cli/cli/commands/mcp.py +524 -0
  51. raise_cli/cli/commands/memory.py +467 -0
  52. raise_cli/cli/commands/pattern.py +348 -0
  53. raise_cli/cli/commands/profile.py +59 -0
  54. raise_cli/cli/commands/publish.py +80 -0
  55. raise_cli/cli/commands/release.py +338 -0
  56. raise_cli/cli/commands/session.py +528 -0
  57. raise_cli/cli/commands/signal.py +410 -0
  58. raise_cli/cli/commands/skill.py +350 -0
  59. raise_cli/cli/commands/skill_set.py +145 -0
  60. raise_cli/cli/error_handler.py +158 -0
  61. raise_cli/cli/main.py +163 -0
  62. raise_cli/compat.py +66 -0
  63. raise_cli/config/__init__.py +41 -0
  64. raise_cli/config/agent_plugin.py +105 -0
  65. raise_cli/config/agent_registry.py +233 -0
  66. raise_cli/config/agents.py +120 -0
  67. raise_cli/config/ide.py +32 -0
  68. raise_cli/config/paths.py +379 -0
  69. raise_cli/config/settings.py +180 -0
  70. raise_cli/context/__init__.py +42 -0
  71. raise_cli/context/analyzers/__init__.py +16 -0
  72. raise_cli/context/analyzers/models.py +36 -0
  73. raise_cli/context/analyzers/protocol.py +43 -0
  74. raise_cli/context/analyzers/python.py +292 -0
  75. raise_cli/context/builder.py +1569 -0
  76. raise_cli/context/diff.py +213 -0
  77. raise_cli/context/extractors/__init__.py +13 -0
  78. raise_cli/context/extractors/skills.py +121 -0
  79. raise_cli/core/__init__.py +37 -0
  80. raise_cli/core/files.py +66 -0
  81. raise_cli/core/text.py +174 -0
  82. raise_cli/core/tools.py +441 -0
  83. raise_cli/discovery/__init__.py +50 -0
  84. raise_cli/discovery/analyzer.py +691 -0
  85. raise_cli/discovery/drift.py +355 -0
  86. raise_cli/discovery/scanner.py +1687 -0
  87. raise_cli/doctor/__init__.py +4 -0
  88. raise_cli/doctor/checks/__init__.py +1 -0
  89. raise_cli/doctor/checks/environment.py +110 -0
  90. raise_cli/doctor/checks/project.py +238 -0
  91. raise_cli/doctor/fix.py +80 -0
  92. raise_cli/doctor/models.py +56 -0
  93. raise_cli/doctor/protocol.py +43 -0
  94. raise_cli/doctor/registry.py +100 -0
  95. raise_cli/doctor/report.py +141 -0
  96. raise_cli/doctor/runner.py +95 -0
  97. raise_cli/engines/__init__.py +3 -0
  98. raise_cli/exceptions.py +215 -0
  99. raise_cli/gates/__init__.py +19 -0
  100. raise_cli/gates/builtin/__init__.py +1 -0
  101. raise_cli/gates/builtin/coverage.py +52 -0
  102. raise_cli/gates/builtin/lint.py +48 -0
  103. raise_cli/gates/builtin/tests.py +48 -0
  104. raise_cli/gates/builtin/types.py +48 -0
  105. raise_cli/gates/models.py +40 -0
  106. raise_cli/gates/protocol.py +41 -0
  107. raise_cli/gates/registry.py +141 -0
  108. raise_cli/governance/__init__.py +11 -0
  109. raise_cli/governance/extractor.py +412 -0
  110. raise_cli/governance/models.py +134 -0
  111. raise_cli/governance/parsers/__init__.py +35 -0
  112. raise_cli/governance/parsers/_convert.py +38 -0
  113. raise_cli/governance/parsers/adr.py +274 -0
  114. raise_cli/governance/parsers/backlog.py +356 -0
  115. raise_cli/governance/parsers/constitution.py +119 -0
  116. raise_cli/governance/parsers/epic.py +323 -0
  117. raise_cli/governance/parsers/glossary.py +316 -0
  118. raise_cli/governance/parsers/guardrails.py +345 -0
  119. raise_cli/governance/parsers/prd.py +112 -0
  120. raise_cli/governance/parsers/roadmap.py +118 -0
  121. raise_cli/governance/parsers/vision.py +116 -0
  122. raise_cli/graph/__init__.py +1 -0
  123. raise_cli/graph/backends/__init__.py +57 -0
  124. raise_cli/graph/backends/api.py +137 -0
  125. raise_cli/graph/backends/dual.py +139 -0
  126. raise_cli/graph/backends/pending.py +84 -0
  127. raise_cli/handlers/__init__.py +3 -0
  128. raise_cli/hooks/__init__.py +54 -0
  129. raise_cli/hooks/builtin/__init__.py +1 -0
  130. raise_cli/hooks/builtin/backlog.py +216 -0
  131. raise_cli/hooks/builtin/gate_bridge.py +83 -0
  132. raise_cli/hooks/builtin/jira_sync.py +127 -0
  133. raise_cli/hooks/builtin/memory.py +117 -0
  134. raise_cli/hooks/builtin/telemetry.py +72 -0
  135. raise_cli/hooks/emitter.py +184 -0
  136. raise_cli/hooks/events.py +262 -0
  137. raise_cli/hooks/protocol.py +38 -0
  138. raise_cli/hooks/registry.py +117 -0
  139. raise_cli/mcp/__init__.py +33 -0
  140. raise_cli/mcp/bridge.py +218 -0
  141. raise_cli/mcp/models.py +43 -0
  142. raise_cli/mcp/registry.py +77 -0
  143. raise_cli/mcp/schema.py +41 -0
  144. raise_cli/memory/__init__.py +58 -0
  145. raise_cli/memory/loader.py +247 -0
  146. raise_cli/memory/migration.py +241 -0
  147. raise_cli/memory/models.py +169 -0
  148. raise_cli/memory/writer.py +598 -0
  149. raise_cli/onboarding/__init__.py +103 -0
  150. raise_cli/onboarding/bootstrap.py +324 -0
  151. raise_cli/onboarding/claudemd.py +17 -0
  152. raise_cli/onboarding/conventions.py +742 -0
  153. raise_cli/onboarding/detection.py +374 -0
  154. raise_cli/onboarding/governance.py +443 -0
  155. raise_cli/onboarding/instructions.py +672 -0
  156. raise_cli/onboarding/manifest.py +201 -0
  157. raise_cli/onboarding/memory_md.py +399 -0
  158. raise_cli/onboarding/migration.py +207 -0
  159. raise_cli/onboarding/profile.py +624 -0
  160. raise_cli/onboarding/skill_conflict.py +100 -0
  161. raise_cli/onboarding/skill_manifest.py +176 -0
  162. raise_cli/onboarding/skills.py +437 -0
  163. raise_cli/onboarding/workflows.py +101 -0
  164. raise_cli/output/__init__.py +28 -0
  165. raise_cli/output/console.py +394 -0
  166. raise_cli/output/formatters/__init__.py +9 -0
  167. raise_cli/output/formatters/adapters.py +135 -0
  168. raise_cli/output/formatters/discover.py +439 -0
  169. raise_cli/output/formatters/skill.py +298 -0
  170. raise_cli/publish/__init__.py +3 -0
  171. raise_cli/publish/changelog.py +80 -0
  172. raise_cli/publish/check.py +179 -0
  173. raise_cli/publish/version.py +172 -0
  174. raise_cli/rai_base/__init__.py +22 -0
  175. raise_cli/rai_base/framework/__init__.py +7 -0
  176. raise_cli/rai_base/framework/methodology.yaml +233 -0
  177. raise_cli/rai_base/governance/__init__.py +1 -0
  178. raise_cli/rai_base/governance/architecture/__init__.py +1 -0
  179. raise_cli/rai_base/governance/architecture/domain-model.md +20 -0
  180. raise_cli/rai_base/governance/architecture/system-context.md +34 -0
  181. raise_cli/rai_base/governance/architecture/system-design.md +24 -0
  182. raise_cli/rai_base/governance/backlog.md +8 -0
  183. raise_cli/rai_base/governance/guardrails.md +17 -0
  184. raise_cli/rai_base/governance/prd.md +25 -0
  185. raise_cli/rai_base/governance/vision.md +16 -0
  186. raise_cli/rai_base/identity/__init__.py +8 -0
  187. raise_cli/rai_base/identity/core.md +119 -0
  188. raise_cli/rai_base/identity/perspective.md +119 -0
  189. raise_cli/rai_base/memory/__init__.py +7 -0
  190. raise_cli/rai_base/memory/patterns-base.jsonl +55 -0
  191. raise_cli/schemas/__init__.py +3 -0
  192. raise_cli/schemas/journal.py +49 -0
  193. raise_cli/schemas/session_state.py +117 -0
  194. raise_cli/session/__init__.py +5 -0
  195. raise_cli/session/bundle.py +820 -0
  196. raise_cli/session/close.py +268 -0
  197. raise_cli/session/journal.py +119 -0
  198. raise_cli/session/resolver.py +126 -0
  199. raise_cli/session/state.py +187 -0
  200. raise_cli/skills/__init__.py +44 -0
  201. raise_cli/skills/locator.py +141 -0
  202. raise_cli/skills/name_checker.py +199 -0
  203. raise_cli/skills/parser.py +145 -0
  204. raise_cli/skills/scaffold.py +212 -0
  205. raise_cli/skills/schema.py +132 -0
  206. raise_cli/skills/skillsets.py +195 -0
  207. raise_cli/skills/validator.py +197 -0
  208. raise_cli/skills_base/__init__.py +80 -0
  209. raise_cli/skills_base/contract-template.md +60 -0
  210. raise_cli/skills_base/preamble.md +37 -0
  211. raise_cli/skills_base/rai-architecture-review/SKILL.md +137 -0
  212. raise_cli/skills_base/rai-debug/SKILL.md +171 -0
  213. raise_cli/skills_base/rai-discover/SKILL.md +167 -0
  214. raise_cli/skills_base/rai-discover-document/SKILL.md +128 -0
  215. raise_cli/skills_base/rai-discover-scan/SKILL.md +147 -0
  216. raise_cli/skills_base/rai-discover-start/SKILL.md +145 -0
  217. raise_cli/skills_base/rai-discover-validate/SKILL.md +142 -0
  218. raise_cli/skills_base/rai-docs-update/SKILL.md +142 -0
  219. raise_cli/skills_base/rai-doctor/SKILL.md +120 -0
  220. raise_cli/skills_base/rai-epic-close/SKILL.md +165 -0
  221. raise_cli/skills_base/rai-epic-close/templates/retrospective.md +68 -0
  222. raise_cli/skills_base/rai-epic-design/SKILL.md +146 -0
  223. raise_cli/skills_base/rai-epic-design/templates/design.md +24 -0
  224. raise_cli/skills_base/rai-epic-design/templates/scope.md +76 -0
  225. raise_cli/skills_base/rai-epic-plan/SKILL.md +153 -0
  226. raise_cli/skills_base/rai-epic-plan/_references/sequencing-strategies.md +67 -0
  227. raise_cli/skills_base/rai-epic-plan/templates/plan-section.md +49 -0
  228. raise_cli/skills_base/rai-epic-run/SKILL.md +208 -0
  229. raise_cli/skills_base/rai-epic-start/SKILL.md +136 -0
  230. raise_cli/skills_base/rai-epic-start/templates/brief.md +34 -0
  231. raise_cli/skills_base/rai-mcp-add/SKILL.md +176 -0
  232. raise_cli/skills_base/rai-mcp-remove/SKILL.md +120 -0
  233. raise_cli/skills_base/rai-mcp-status/SKILL.md +147 -0
  234. raise_cli/skills_base/rai-problem-shape/SKILL.md +138 -0
  235. raise_cli/skills_base/rai-project-create/SKILL.md +144 -0
  236. raise_cli/skills_base/rai-project-onboard/SKILL.md +162 -0
  237. raise_cli/skills_base/rai-quality-review/SKILL.md +189 -0
  238. raise_cli/skills_base/rai-research/SKILL.md +143 -0
  239. raise_cli/skills_base/rai-research/references/research-prompt-template.md +317 -0
  240. raise_cli/skills_base/rai-session-close/SKILL.md +176 -0
  241. raise_cli/skills_base/rai-session-start/SKILL.md +110 -0
  242. raise_cli/skills_base/rai-story-close/SKILL.md +198 -0
  243. raise_cli/skills_base/rai-story-design/SKILL.md +203 -0
  244. raise_cli/skills_base/rai-story-design/references/tech-design-story-v2.md +293 -0
  245. raise_cli/skills_base/rai-story-implement/SKILL.md +115 -0
  246. raise_cli/skills_base/rai-story-plan/SKILL.md +135 -0
  247. raise_cli/skills_base/rai-story-review/SKILL.md +178 -0
  248. raise_cli/skills_base/rai-story-run/SKILL.md +282 -0
  249. raise_cli/skills_base/rai-story-start/SKILL.md +166 -0
  250. raise_cli/skills_base/rai-story-start/templates/story.md +38 -0
  251. raise_cli/skills_base/rai-welcome/SKILL.md +134 -0
  252. raise_cli/telemetry/__init__.py +42 -0
  253. raise_cli/telemetry/schemas.py +285 -0
  254. raise_cli/telemetry/writer.py +217 -0
  255. raise_cli/tier/__init__.py +0 -0
  256. raise_cli/tier/context.py +134 -0
  257. raise_cli/viz/__init__.py +7 -0
  258. raise_cli/viz/generator.py +406 -0
  259. raise_cli-2.2.1.dist-info/METADATA +433 -0
  260. raise_cli-2.2.1.dist-info/RECORD +264 -0
  261. raise_cli-2.2.1.dist-info/WHEEL +4 -0
  262. raise_cli-2.2.1.dist-info/entry_points.txt +40 -0
  263. raise_cli-2.2.1.dist-info/licenses/LICENSE +190 -0
  264. raise_cli-2.2.1.dist-info/licenses/NOTICE +4 -0
@@ -0,0 +1,43 @@
1
+ """MCP infrastructure models — independent of domain adapters.
2
+
3
+ These models belong to the MCP infrastructure layer. Domain adapters
4
+ (PM, Docs) have their own models in ``raise_cli.adapters.models``.
5
+
6
+ Architecture: ADR-042, E338 (AR-C1)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ from pydantic import BaseModel, Field
14
+
15
+
16
+ class McpToolResult(BaseModel):
17
+ """Parsed result from an MCP tool call."""
18
+
19
+ text: str = ""
20
+ data: dict[str, Any] = Field(default_factory=dict)
21
+ is_error: bool = False
22
+ error_message: str = ""
23
+
24
+
25
+ class McpToolInfo(BaseModel):
26
+ """Tool metadata from server discovery."""
27
+
28
+ name: str
29
+ description: str = ""
30
+
31
+
32
+ class McpHealthResult(BaseModel):
33
+ """Health check result for an MCP server.
34
+
35
+ Independent of AdapterHealth (domain layer). Adapters convert
36
+ McpHealthResult → AdapterHealth when needed.
37
+ """
38
+
39
+ server_name: str = Field(..., description="MCP server identifier")
40
+ healthy: bool = Field(..., description="Whether server responded")
41
+ message: str = Field(default="", description="Status or error message")
42
+ latency_ms: int | None = Field(default=None, description="Response latency in ms")
43
+ tool_count: int = Field(default=0, description="Number of tools discovered")
@@ -0,0 +1,77 @@
1
+ """MCP server registry — discovers servers from .raise/mcp/*.yaml.
2
+
3
+ Scans the MCP config directory for YAML files, parses each as
4
+ ``McpServerConfig``, and returns a name → config mapping.
5
+
6
+ Architecture: ADR-042, E338
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from pathlib import Path
13
+
14
+ import yaml
15
+ from pydantic import ValidationError
16
+
17
+ from raise_cli.mcp.schema import McpServerConfig
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def discover_mcp_servers(
23
+ mcp_dir: Path | None = None,
24
+ ) -> dict[str, McpServerConfig]:
25
+ """Discover MCP servers registered in .raise/mcp/*.yaml.
26
+
27
+ Scans ``mcp_dir`` (default: ``.raise/mcp/``) for ``*.yaml`` files,
28
+ parses each as ``McpServerConfig``, and returns a name → config mapping.
29
+
30
+ Args:
31
+ mcp_dir: Directory to scan. Defaults to ``.raise/mcp/``.
32
+
33
+ Returns:
34
+ Mapping of server name to parsed config.
35
+ """
36
+ if mcp_dir is None:
37
+ mcp_dir = Path.cwd() / ".raise" / "mcp"
38
+
39
+ if not mcp_dir.is_dir():
40
+ return {}
41
+
42
+ result: dict[str, McpServerConfig] = {}
43
+
44
+ for yaml_path in sorted(mcp_dir.glob("*.yaml")):
45
+ if yaml_path.name == "catalog.yaml":
46
+ continue
47
+
48
+ try:
49
+ raw = yaml.safe_load(yaml_path.read_text(encoding="utf-8"))
50
+ except Exception as exc: # noqa: BLE001
51
+ logger.warning("Skipping %s: YAML parse error: %s", yaml_path.name, exc)
52
+ continue
53
+
54
+ if not isinstance(raw, dict):
55
+ logger.warning("Skipping %s: YAML content is not a mapping", yaml_path.name)
56
+ continue
57
+
58
+ try:
59
+ config = McpServerConfig.model_validate(raw)
60
+ except ValidationError as exc:
61
+ logger.warning(
62
+ "Skipping %s: schema validation error: %s", yaml_path.name, exc
63
+ )
64
+ continue
65
+
66
+ name = config.name
67
+ if name in result:
68
+ logger.warning(
69
+ "Skipping %s: server name '%s' already defined by another YAML file",
70
+ yaml_path.name,
71
+ name,
72
+ )
73
+ continue
74
+
75
+ result[name] = config
76
+
77
+ return result
@@ -0,0 +1,41 @@
1
+ """MCP server configuration schema.
2
+
3
+ Defines the YAML config format for ``.raise/mcp/*.yaml`` and the shared
4
+ ``ServerConnection`` model used by both MCP registry and declarative adapters.
5
+
6
+ Architecture: ADR-042, E338 (AR-C2)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pydantic import BaseModel, Field
12
+
13
+
14
+ class ServerConnection(BaseModel):
15
+ """MCP server connection configuration.
16
+
17
+ Shared between MCP registry (``McpServerConfig``) and declarative
18
+ adapters (``DeclarativeAdapterConfig``). Eliminates duplication (AR-C2).
19
+ """
20
+
21
+ command: str = Field(..., description="Server command (e.g. 'uvx', 'npx')")
22
+ args: list[str] = Field(
23
+ default_factory=list, description="Server command arguments"
24
+ )
25
+ env: list[str] | None = Field(
26
+ default=None,
27
+ description="Env var names to pass to server subprocess",
28
+ )
29
+
30
+
31
+ class McpServerConfig(BaseModel):
32
+ """Root config model for a generic MCP server.
33
+
34
+ Parsed from ``.raise/mcp/<name>.yaml``. Not tied to any domain protocol.
35
+ """
36
+
37
+ name: str = Field(..., description="Server name (used in rai mcp commands)")
38
+ description: str | None = Field(
39
+ default=None, description="Human-readable description"
40
+ )
41
+ server: ServerConnection
@@ -0,0 +1,58 @@
1
+ """Memory module for Rai's persistent memory.
2
+
3
+ This module provides infrastructure for loading, querying, and managing
4
+ Rai's accumulated memories stored in JSONL format.
5
+
6
+ For queries, use the Graph from raise_cli.context:
7
+ - CLI: `raise context query "keywords" --types pattern,calibration,session`
8
+ - Programmatic: `QueryEngine` from `raise_cli.context.query`
9
+
10
+ The JSONL files (.raise/rai/memory/*.jsonl) remain the source of truth.
11
+ The unified graph consolidates memory with governance, skills, and work items.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from raise_cli.memory.migration import (
17
+ MigrationResult,
18
+ migrate_to_personal,
19
+ needs_migration,
20
+ )
21
+ from raise_cli.memory.models import (
22
+ MemoryConcept,
23
+ MemoryConceptType,
24
+ MemoryRelationship,
25
+ MemoryRelationshipType,
26
+ MemoryScope,
27
+ PatternSubType,
28
+ )
29
+ from raise_cli.memory.writer import (
30
+ PatternInput,
31
+ ReinforceResult,
32
+ SessionInput,
33
+ WriteResult,
34
+ append_pattern,
35
+ append_session,
36
+ get_memory_dir_for_scope,
37
+ reinforce_pattern,
38
+ )
39
+
40
+ __all__ = [
41
+ "MemoryConcept",
42
+ "MemoryConceptType",
43
+ "MemoryRelationship",
44
+ "MemoryRelationshipType",
45
+ "MemoryScope",
46
+ "MigrationResult",
47
+ "PatternInput",
48
+ "PatternSubType",
49
+ "ReinforceResult",
50
+ "SessionInput",
51
+ "WriteResult",
52
+ "append_pattern",
53
+ "append_session",
54
+ "get_memory_dir_for_scope",
55
+ "migrate_to_personal",
56
+ "needs_migration",
57
+ "reinforce_pattern",
58
+ ]
@@ -0,0 +1,247 @@
1
+ """JSONL loader for memory concepts.
2
+
3
+ This module provides functions to load memory concepts from JSONL files
4
+ in the .raise/rai/memory/ directory.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from datetime import date
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from raise_cli.memory.models import (
15
+ MemoryConcept,
16
+ MemoryConceptType,
17
+ MemoryLoadResult,
18
+ MemoryScope,
19
+ )
20
+
21
+
22
+ def parse_date(date_str: str) -> date:
23
+ """Parse a date string in YYYY-MM-DD format.
24
+
25
+ Args:
26
+ date_str: Date string to parse.
27
+
28
+ Returns:
29
+ Parsed date object.
30
+
31
+ Raises:
32
+ ValueError: If date string is invalid.
33
+ """
34
+ return date.fromisoformat(date_str)
35
+
36
+
37
+ def load_pattern(
38
+ data: dict[str, Any], scope: MemoryScope = MemoryScope.PROJECT
39
+ ) -> MemoryConcept:
40
+ """Load a pattern concept from JSONL data.
41
+
42
+ Args:
43
+ data: Dictionary from JSONL line.
44
+ scope: Memory scope (global, project, or personal).
45
+
46
+ Returns:
47
+ MemoryConcept for the pattern.
48
+ """
49
+ # Handle schema variations: 'content' or 'pattern' field
50
+ content = data.get("content") or data.get("pattern", "")
51
+ # Handle date field variations: 'created' or 'date'
52
+ date_str = data.get("created") or data.get("date", "")
53
+
54
+ metadata: dict[str, Any] = {
55
+ "sub_type": data.get("type", "unknown"),
56
+ "learned_from": data.get("learned_from"),
57
+ "scope": scope.value,
58
+ }
59
+
60
+ # Surface base/version for pattern versioning (F14.6)
61
+ if data.get("base") is not None:
62
+ metadata["base"] = data["base"]
63
+ if data.get("version") is not None:
64
+ metadata["version"] = data["version"]
65
+
66
+ return MemoryConcept(
67
+ id=data["id"],
68
+ type=MemoryConceptType.PATTERN,
69
+ content=content,
70
+ context=data.get("context", []),
71
+ created=parse_date(date_str),
72
+ metadata=metadata,
73
+ )
74
+
75
+
76
+ def load_calibration(
77
+ data: dict[str, Any], scope: MemoryScope = MemoryScope.PROJECT
78
+ ) -> MemoryConcept:
79
+ """Load a calibration concept from JSONL data.
80
+
81
+ Args:
82
+ data: Dictionary from JSONL line.
83
+ scope: Memory scope (global, project, or personal).
84
+
85
+ Returns:
86
+ MemoryConcept for the calibration.
87
+ """
88
+ # Handle schema variations for name and story fields
89
+ name = data.get("name") or data.get("feature_name", "unknown")
90
+ # Backward compat: try "story" (new), then "feature" (old data)
91
+ story = data.get("story") or data.get("feature") or data.get("story_id", "unknown")
92
+ # Handle date field variations: 'created' or 'date'
93
+ date_str = data.get("created") or data.get("date", "")
94
+ # Handle velocity field variations: 'ratio' or 'velocity'
95
+ velocity = data.get("ratio") or data.get("velocity")
96
+
97
+ # Build content summary from calibration data
98
+ content_parts = [f"{name} ({story})"]
99
+ if data.get("actual_min"):
100
+ content_parts.append(f"actual: {data['actual_min']}min")
101
+ if velocity:
102
+ content_parts.append(f"velocity: {velocity}x")
103
+ content = " - ".join(content_parts)
104
+
105
+ # Build context from story and size
106
+ context = [story, data["size"].lower()]
107
+ if data.get("kata_cycle"):
108
+ context.append("kata-cycle")
109
+
110
+ return MemoryConcept(
111
+ id=data.get("id") or data.get("story_id", "unknown"),
112
+ type=MemoryConceptType.CALIBRATION,
113
+ content=content,
114
+ context=context,
115
+ created=parse_date(date_str),
116
+ metadata={
117
+ "story": story,
118
+ "name": name,
119
+ "size": data["size"],
120
+ "sp": data.get("sp"),
121
+ "estimated_min": data.get("estimated_min"),
122
+ "actual_min": data.get("actual_min"),
123
+ "ratio": velocity,
124
+ "kata_cycle": data.get("kata_cycle", False),
125
+ "notes": data.get("notes"),
126
+ "scope": scope.value,
127
+ },
128
+ )
129
+
130
+
131
+ def load_session(
132
+ data: dict[str, Any], scope: MemoryScope = MemoryScope.PROJECT
133
+ ) -> MemoryConcept:
134
+ """Load a session concept from JSONL data.
135
+
136
+ Args:
137
+ data: Dictionary from JSONL line.
138
+ scope: Memory scope (global, project, or personal).
139
+
140
+ Returns:
141
+ MemoryConcept for the session.
142
+ """
143
+ # Handle schema variations: 'topic' or 'summary'
144
+ topic = data.get("topic") or data.get("summary", "unknown")
145
+
146
+ # Build content from topic and outcomes
147
+ outcomes_str = ", ".join(data.get("outcomes", [])[:3])
148
+ content = f"{topic}: {outcomes_str}"
149
+
150
+ # Build context from type and outcomes keywords
151
+ context = [data["type"]]
152
+ # Extract keywords from topic
153
+ topic_words = topic.lower().split()
154
+ context.extend([w for w in topic_words if len(w) > 3][:3])
155
+
156
+ return MemoryConcept(
157
+ id=data["id"],
158
+ type=MemoryConceptType.SESSION,
159
+ content=content,
160
+ context=context,
161
+ created=parse_date(data["date"]),
162
+ metadata={
163
+ "session_type": data["type"],
164
+ "topic": topic,
165
+ "outcomes": data.get("outcomes", []),
166
+ "log_path": data.get("log_path"),
167
+ "scope": scope.value,
168
+ },
169
+ )
170
+
171
+
172
+ def load_jsonl_file(
173
+ file_path: Path,
174
+ concept_type: MemoryConceptType,
175
+ scope: MemoryScope = MemoryScope.PROJECT,
176
+ ) -> tuple[list[MemoryConcept], list[str]]:
177
+ """Load concepts from a single JSONL file.
178
+
179
+ Args:
180
+ file_path: Path to the JSONL file.
181
+ concept_type: Type of concepts in the file.
182
+ scope: Memory scope to assign to loaded concepts.
183
+
184
+ Returns:
185
+ Tuple of (concepts list, errors list).
186
+ """
187
+ concepts: list[MemoryConcept] = []
188
+ errors: list[str] = []
189
+
190
+ if not file_path.exists():
191
+ return concepts, errors
192
+
193
+ loader_map = {
194
+ MemoryConceptType.PATTERN: load_pattern,
195
+ MemoryConceptType.CALIBRATION: load_calibration,
196
+ MemoryConceptType.SESSION: load_session,
197
+ }
198
+ loader = loader_map[concept_type]
199
+
200
+ with file_path.open("r", encoding="utf-8") as f:
201
+ for line_num, line in enumerate(f, start=1):
202
+ line = line.strip()
203
+ if not line:
204
+ continue
205
+ try:
206
+ data = json.loads(line)
207
+ concept = loader(data, scope=scope)
208
+ concepts.append(concept)
209
+ except (json.JSONDecodeError, KeyError, ValueError) as e:
210
+ errors.append(f"{file_path.name}:{line_num}: {e}")
211
+
212
+ return concepts, errors
213
+
214
+
215
+ def load_memory_from_directory(memory_dir: Path) -> MemoryLoadResult:
216
+ """Load all memory concepts from a .raise/rai/memory/ directory.
217
+
218
+ Args:
219
+ memory_dir: Path to the memory directory.
220
+
221
+ Returns:
222
+ MemoryLoadResult with all loaded concepts.
223
+ """
224
+ all_concepts: list[MemoryConcept] = []
225
+ all_errors: list[str] = []
226
+ files_processed = 0
227
+
228
+ # Define files and their types
229
+ file_mappings = [
230
+ (memory_dir / "patterns.jsonl", MemoryConceptType.PATTERN),
231
+ (memory_dir / "calibration.jsonl", MemoryConceptType.CALIBRATION),
232
+ (memory_dir / "sessions" / "index.jsonl", MemoryConceptType.SESSION),
233
+ ]
234
+
235
+ for file_path, concept_type in file_mappings:
236
+ if file_path.exists():
237
+ concepts, errors = load_jsonl_file(file_path, concept_type)
238
+ all_concepts.extend(concepts)
239
+ all_errors.extend(errors)
240
+ files_processed += 1
241
+
242
+ return MemoryLoadResult(
243
+ concepts=all_concepts,
244
+ total=len(all_concepts),
245
+ files_processed=files_processed,
246
+ errors=all_errors,
247
+ )