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,528 @@
|
|
|
1
|
+
"""CLI commands for session management.
|
|
2
|
+
|
|
3
|
+
This module provides the `raise session` command group for managing
|
|
4
|
+
working sessions — the lifecycle of a developer's focused work period.
|
|
5
|
+
|
|
6
|
+
Sessions are first-class workflow state, distinct from:
|
|
7
|
+
- Profile (developer identity)
|
|
8
|
+
- Memory (persistent knowledge)
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
$ raise session start # Start a new session
|
|
12
|
+
$ raise session start --context # Start with context bundle
|
|
13
|
+
$ raise session close # End the current session
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Annotated
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
|
|
23
|
+
from raise_cli.cli.commands.journal import journal_app
|
|
24
|
+
from raise_cli.cli.error_handler import cli_error
|
|
25
|
+
from raise_cli.exceptions import RaiSessionNotFoundError
|
|
26
|
+
from raise_cli.hooks.emitter import create_emitter
|
|
27
|
+
from raise_cli.hooks.events import (
|
|
28
|
+
BeforeSessionCloseEvent,
|
|
29
|
+
SessionCloseEvent,
|
|
30
|
+
SessionStartEvent,
|
|
31
|
+
)
|
|
32
|
+
from raise_cli.memory.writer import get_next_id, validate_session_index
|
|
33
|
+
from raise_cli.onboarding.profile import (
|
|
34
|
+
DeveloperProfile,
|
|
35
|
+
end_session,
|
|
36
|
+
increment_session,
|
|
37
|
+
load_developer_profile,
|
|
38
|
+
save_developer_profile,
|
|
39
|
+
start_session,
|
|
40
|
+
)
|
|
41
|
+
from raise_cli.session.bundle import assemble_context_bundle, assemble_sections
|
|
42
|
+
from raise_cli.session.close import CloseInput, load_state_file, process_session_close
|
|
43
|
+
from raise_cli.session.resolver import resolve_session_id
|
|
44
|
+
from raise_cli.session.state import (
|
|
45
|
+
cleanup_session_dir,
|
|
46
|
+
load_session_state,
|
|
47
|
+
migrate_flat_to_session,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
session_app = typer.Typer(
|
|
51
|
+
name="session",
|
|
52
|
+
help="Manage working sessions",
|
|
53
|
+
no_args_is_help=True,
|
|
54
|
+
)
|
|
55
|
+
session_app.add_typer(journal_app, name="journal")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _check_cwd_guard(
|
|
59
|
+
profile: DeveloperProfile,
|
|
60
|
+
session_id: str,
|
|
61
|
+
close_project: Path,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Poka-yoke: reject session close if CWD project != session project.
|
|
64
|
+
|
|
65
|
+
Compares the resolved absolute path of the close project against the
|
|
66
|
+
project recorded in the ActiveSession. Raises cli_error on mismatch.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
profile: Developer profile with active sessions.
|
|
70
|
+
session_id: The session being closed.
|
|
71
|
+
close_project: Project path from --project flag or CWD.
|
|
72
|
+
"""
|
|
73
|
+
for active in profile.active_sessions:
|
|
74
|
+
if active.session_id == session_id and active.project:
|
|
75
|
+
session_path = Path(active.project).resolve()
|
|
76
|
+
close_path = close_project.resolve()
|
|
77
|
+
if session_path != close_path:
|
|
78
|
+
cli_error(
|
|
79
|
+
f"CWD mismatch — session {session_id} started in "
|
|
80
|
+
f"{session_path} but close is running from {close_path}. "
|
|
81
|
+
f"Run from the correct project directory, or use "
|
|
82
|
+
f"--project {session_path}.",
|
|
83
|
+
)
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@session_app.command()
|
|
88
|
+
def start(
|
|
89
|
+
name: Annotated[
|
|
90
|
+
str | None,
|
|
91
|
+
typer.Option(
|
|
92
|
+
"--name",
|
|
93
|
+
"-n",
|
|
94
|
+
help="Your name (required for first-time setup)",
|
|
95
|
+
),
|
|
96
|
+
] = None,
|
|
97
|
+
project: Annotated[
|
|
98
|
+
str | None,
|
|
99
|
+
typer.Option(
|
|
100
|
+
"--project",
|
|
101
|
+
"-p",
|
|
102
|
+
help="Project path to associate with this session",
|
|
103
|
+
),
|
|
104
|
+
] = None,
|
|
105
|
+
agent: Annotated[
|
|
106
|
+
str | None,
|
|
107
|
+
typer.Option(
|
|
108
|
+
"--agent",
|
|
109
|
+
help="Agent type (e.g., claude-code, cursor). Default: unknown",
|
|
110
|
+
),
|
|
111
|
+
] = None,
|
|
112
|
+
context: Annotated[
|
|
113
|
+
bool,
|
|
114
|
+
typer.Option(
|
|
115
|
+
"--context",
|
|
116
|
+
help="Output a context bundle for AI consumption",
|
|
117
|
+
),
|
|
118
|
+
] = False,
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Start a new working session.
|
|
121
|
+
|
|
122
|
+
Increments the session counter and sets active session state.
|
|
123
|
+
Checks for orphaned sessions (started but not closed) and warns if found.
|
|
124
|
+
For first-time users, creates a new developer profile.
|
|
125
|
+
|
|
126
|
+
With --context, outputs a token-optimized context bundle (~150 tokens)
|
|
127
|
+
assembled from the developer profile, session state, and memory graph.
|
|
128
|
+
|
|
129
|
+
Examples:
|
|
130
|
+
$ raise session start # Start session
|
|
131
|
+
$ raise session start --name "Alice" # First-time setup
|
|
132
|
+
$ raise session start --project /my/proj # Start with project path
|
|
133
|
+
$ raise session start --project . --context # Context bundle
|
|
134
|
+
"""
|
|
135
|
+
profile = load_developer_profile()
|
|
136
|
+
|
|
137
|
+
if profile is None:
|
|
138
|
+
# First-time user - need name to create profile
|
|
139
|
+
if name is None:
|
|
140
|
+
cli_error(
|
|
141
|
+
"No developer profile found",
|
|
142
|
+
hint="Provide --name for first-time setup: raise session start --name 'Your Name'",
|
|
143
|
+
)
|
|
144
|
+
return # cli_error raises, but this helps pyright
|
|
145
|
+
|
|
146
|
+
# Create new profile
|
|
147
|
+
profile = DeveloperProfile(name=name)
|
|
148
|
+
typer.echo(f"Welcome to RaiSE, {name}! Creating your developer profile...")
|
|
149
|
+
|
|
150
|
+
# Check for active session
|
|
151
|
+
if profile.current_session is not None:
|
|
152
|
+
prev = profile.current_session
|
|
153
|
+
if prev.is_stale():
|
|
154
|
+
typer.echo(
|
|
155
|
+
f"Warning: Stale session detected (started {prev.started_at.date()}, "
|
|
156
|
+
f"project: {prev.project})\n"
|
|
157
|
+
"Previous session was not closed. Learnings may have been lost.\n"
|
|
158
|
+
"Tip: Use /rai-session-close before ending work."
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
typer.echo(
|
|
162
|
+
f"Note: Session already active (project: {prev.project})\n"
|
|
163
|
+
"Starting new session anyway. Previous session not closed."
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Jidoka: Validate session index if project specified
|
|
167
|
+
if project is not None:
|
|
168
|
+
personal_dir = Path(project) / ".raise" / "rai" / "personal"
|
|
169
|
+
if personal_dir.exists():
|
|
170
|
+
validation = validate_session_index(personal_dir)
|
|
171
|
+
if not validation.is_valid:
|
|
172
|
+
typer.echo(f"Warning: {validation.summary()}")
|
|
173
|
+
typer.echo("Run `raise memory validate` to fix data quality issues.\n")
|
|
174
|
+
|
|
175
|
+
# Increment session count
|
|
176
|
+
updated = increment_session(profile, project_path=project)
|
|
177
|
+
|
|
178
|
+
# Generate session ID and add to active_sessions
|
|
179
|
+
session_id: str | None = None
|
|
180
|
+
if project is not None:
|
|
181
|
+
personal_dir = Path(project) / ".raise" / "rai" / "personal"
|
|
182
|
+
sessions_index = personal_dir / "sessions" / "index.jsonl"
|
|
183
|
+
session_id = get_next_id(sessions_index, "SES")
|
|
184
|
+
|
|
185
|
+
# Migrate flat files if they exist (before creating dir)
|
|
186
|
+
migrate_flat_to_session(Path(project), session_id)
|
|
187
|
+
|
|
188
|
+
# Ensure per-session directory exists (migration may have created it)
|
|
189
|
+
session_dir = (
|
|
190
|
+
Path(project) / ".raise" / "rai" / "personal" / "sessions" / session_id
|
|
191
|
+
)
|
|
192
|
+
session_dir.mkdir(parents=True, exist_ok=True)
|
|
193
|
+
|
|
194
|
+
# Add to active_sessions (with stale warning)
|
|
195
|
+
agent_name = agent if agent else "unknown"
|
|
196
|
+
updated, stale_sessions = start_session(
|
|
197
|
+
updated, session_id=session_id, project_path=project, agent=agent_name
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Warn about stale sessions
|
|
201
|
+
if stale_sessions:
|
|
202
|
+
typer.echo("⚠ Warning: Stale sessions detected (started >24h ago):")
|
|
203
|
+
for stale in stale_sessions:
|
|
204
|
+
typer.echo(
|
|
205
|
+
f" - {stale.session_id} (started {stale.started_at.date()}, project: {stale.project})"
|
|
206
|
+
)
|
|
207
|
+
typer.echo(
|
|
208
|
+
"Consider closing these sessions with: rai session close --session <ID>\n"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
save_developer_profile(updated)
|
|
212
|
+
|
|
213
|
+
# Emit session:start event
|
|
214
|
+
emitter = create_emitter()
|
|
215
|
+
emitter.emit(
|
|
216
|
+
SessionStartEvent(
|
|
217
|
+
session_id=session_id or "",
|
|
218
|
+
developer=updated.name,
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Format agent for output
|
|
223
|
+
agent_name = agent if agent else "unknown"
|
|
224
|
+
|
|
225
|
+
if context and project is not None:
|
|
226
|
+
project_path = Path(project)
|
|
227
|
+
# Load state from per-session dir (migration moved flat file there)
|
|
228
|
+
# Falls back to flat file if no session_id
|
|
229
|
+
state = load_session_state(project_path, session_id=session_id)
|
|
230
|
+
bundle = assemble_context_bundle(
|
|
231
|
+
updated, state, project_path, session_id=session_id
|
|
232
|
+
)
|
|
233
|
+
typer.echo(bundle)
|
|
234
|
+
else:
|
|
235
|
+
if session_id:
|
|
236
|
+
typer.echo(f"▶ Session {session_id} started ({agent_name})")
|
|
237
|
+
else:
|
|
238
|
+
typer.echo(f"▶ Session started ({agent_name})")
|
|
239
|
+
typer.echo(f"Session recorded. (last: {updated.last_session})")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@session_app.command()
|
|
243
|
+
def context(
|
|
244
|
+
sections: Annotated[
|
|
245
|
+
str,
|
|
246
|
+
typer.Option(
|
|
247
|
+
"--sections",
|
|
248
|
+
"-s",
|
|
249
|
+
help="Comma-separated section names to load (e.g., 'governance,behavioral')",
|
|
250
|
+
),
|
|
251
|
+
],
|
|
252
|
+
project: Annotated[
|
|
253
|
+
str,
|
|
254
|
+
typer.Option(
|
|
255
|
+
"--project",
|
|
256
|
+
"-p",
|
|
257
|
+
help="Project path",
|
|
258
|
+
),
|
|
259
|
+
],
|
|
260
|
+
) -> None:
|
|
261
|
+
"""Load specific context sections for AI consumption.
|
|
262
|
+
|
|
263
|
+
Returns formatted priming sections selected by name. Use after
|
|
264
|
+
`rai session start --context` to load task-relevant context.
|
|
265
|
+
|
|
266
|
+
Available sections: governance, behavioral, coaching, deadlines, progress.
|
|
267
|
+
|
|
268
|
+
Examples:
|
|
269
|
+
$ raise session context --sections governance,behavioral --project .
|
|
270
|
+
$ raise session context --sections coaching --project /my/proj
|
|
271
|
+
"""
|
|
272
|
+
profile = load_developer_profile()
|
|
273
|
+
if profile is None:
|
|
274
|
+
cli_error("No developer profile found")
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
project_path = Path(project)
|
|
278
|
+
state = load_session_state(project_path)
|
|
279
|
+
|
|
280
|
+
section_list = [s.strip() for s in sections.split(",") if s.strip()]
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
output = assemble_sections(section_list, project_path, profile, state)
|
|
284
|
+
except ValueError as e:
|
|
285
|
+
cli_error(str(e))
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
if output:
|
|
289
|
+
typer.echo(output)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@session_app.command()
|
|
293
|
+
def close(
|
|
294
|
+
summary: Annotated[
|
|
295
|
+
str | None,
|
|
296
|
+
typer.Option(
|
|
297
|
+
"--summary",
|
|
298
|
+
"-s",
|
|
299
|
+
help="Session summary",
|
|
300
|
+
),
|
|
301
|
+
] = None,
|
|
302
|
+
session_type: Annotated[
|
|
303
|
+
str | None,
|
|
304
|
+
typer.Option(
|
|
305
|
+
"--type",
|
|
306
|
+
"-t",
|
|
307
|
+
help="Session type (feature, research, maintenance, etc.)",
|
|
308
|
+
),
|
|
309
|
+
] = None,
|
|
310
|
+
pattern: Annotated[
|
|
311
|
+
str | None,
|
|
312
|
+
typer.Option(
|
|
313
|
+
"--pattern",
|
|
314
|
+
help="Pattern description to record (format: 'description')",
|
|
315
|
+
),
|
|
316
|
+
] = None,
|
|
317
|
+
correction: Annotated[
|
|
318
|
+
str | None,
|
|
319
|
+
typer.Option(
|
|
320
|
+
"--correction",
|
|
321
|
+
help="Coaching correction observed",
|
|
322
|
+
),
|
|
323
|
+
] = None,
|
|
324
|
+
correction_lesson: Annotated[
|
|
325
|
+
str | None,
|
|
326
|
+
typer.Option(
|
|
327
|
+
"--correction-lesson",
|
|
328
|
+
help="Lesson from the correction",
|
|
329
|
+
),
|
|
330
|
+
] = None,
|
|
331
|
+
state_file: Annotated[
|
|
332
|
+
str | None,
|
|
333
|
+
typer.Option(
|
|
334
|
+
"--state-file",
|
|
335
|
+
help="YAML file with full structured session output",
|
|
336
|
+
),
|
|
337
|
+
] = None,
|
|
338
|
+
session: Annotated[
|
|
339
|
+
str | None,
|
|
340
|
+
typer.Option(
|
|
341
|
+
"--session",
|
|
342
|
+
help="Session ID to close (e.g., SES-177). Falls back to RAI_SESSION_ID env var.",
|
|
343
|
+
),
|
|
344
|
+
] = None,
|
|
345
|
+
project: Annotated[
|
|
346
|
+
str | None,
|
|
347
|
+
typer.Option(
|
|
348
|
+
"--project",
|
|
349
|
+
"-p",
|
|
350
|
+
help="Project path",
|
|
351
|
+
),
|
|
352
|
+
] = None,
|
|
353
|
+
) -> None:
|
|
354
|
+
"""End the current working session.
|
|
355
|
+
|
|
356
|
+
With no flags: clears active session state (legacy behavior).
|
|
357
|
+
With --summary or --state-file: performs full structured close —
|
|
358
|
+
records session, patterns, corrections, and updates state.
|
|
359
|
+
|
|
360
|
+
All writes are performed atomically by the CLI — skills should
|
|
361
|
+
NOT call separate telemetry/memory commands.
|
|
362
|
+
|
|
363
|
+
Examples:
|
|
364
|
+
$ raise session close
|
|
365
|
+
$ raise session close --summary "Session protocol design" --type feature
|
|
366
|
+
$ raise session close --state-file /tmp/session-output.yaml --project .
|
|
367
|
+
"""
|
|
368
|
+
profile = load_developer_profile()
|
|
369
|
+
|
|
370
|
+
if profile is None:
|
|
371
|
+
cli_error("No developer profile found")
|
|
372
|
+
return # cli_error raises, but this helps pyright
|
|
373
|
+
|
|
374
|
+
# Resolve session ID (from --session flag or RAI_SESSION_ID env var)
|
|
375
|
+
resolved_session_id: str | None = None
|
|
376
|
+
if session:
|
|
377
|
+
import os
|
|
378
|
+
|
|
379
|
+
try:
|
|
380
|
+
resolved_session_id = resolve_session_id(
|
|
381
|
+
session_flag=session, env_var=os.getenv("RAI_SESSION_ID")
|
|
382
|
+
)
|
|
383
|
+
except RaiSessionNotFoundError as e:
|
|
384
|
+
cli_error(str(e))
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
# Determine if this is a structured close
|
|
388
|
+
is_structured = summary is not None or state_file is not None
|
|
389
|
+
|
|
390
|
+
if not is_structured:
|
|
391
|
+
# Legacy behavior: just clear active session
|
|
392
|
+
legacy_project = Path(project) if project else Path.cwd()
|
|
393
|
+
if not resolved_session_id:
|
|
394
|
+
# No session specified — find active session for THIS project
|
|
395
|
+
resolved_project = legacy_project.resolve()
|
|
396
|
+
for active in profile.active_sessions:
|
|
397
|
+
if (
|
|
398
|
+
active.project
|
|
399
|
+
and Path(active.project).resolve() == resolved_project
|
|
400
|
+
):
|
|
401
|
+
resolved_session_id = active.session_id
|
|
402
|
+
break
|
|
403
|
+
if not resolved_session_id:
|
|
404
|
+
if not profile.active_sessions:
|
|
405
|
+
typer.echo("No active session to close.")
|
|
406
|
+
else:
|
|
407
|
+
typer.echo("No active session for this project.")
|
|
408
|
+
return
|
|
409
|
+
|
|
410
|
+
# CWD poka-yoke (RAISE-139): reject if project mismatch
|
|
411
|
+
_check_cwd_guard(profile, resolved_session_id, legacy_project)
|
|
412
|
+
|
|
413
|
+
# Emit before:session:close — hooks can abort
|
|
414
|
+
emitter = create_emitter()
|
|
415
|
+
before_result = emitter.emit(
|
|
416
|
+
BeforeSessionCloseEvent(
|
|
417
|
+
session_id=resolved_session_id,
|
|
418
|
+
outcome="legacy",
|
|
419
|
+
)
|
|
420
|
+
)
|
|
421
|
+
if before_result.aborted:
|
|
422
|
+
typer.echo(f"Session close aborted: {before_result.abort_message}")
|
|
423
|
+
raise typer.Exit(1)
|
|
424
|
+
|
|
425
|
+
updated = end_session(profile, session_id=resolved_session_id)
|
|
426
|
+
save_developer_profile(updated)
|
|
427
|
+
|
|
428
|
+
emitter.emit(
|
|
429
|
+
SessionCloseEvent(
|
|
430
|
+
session_id=resolved_session_id,
|
|
431
|
+
outcome="legacy",
|
|
432
|
+
)
|
|
433
|
+
)
|
|
434
|
+
typer.echo(f"Session {resolved_session_id} closed.")
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
# Structured close: build CloseInput from flags or state file
|
|
438
|
+
if state_file is not None:
|
|
439
|
+
try:
|
|
440
|
+
close_input = load_state_file(Path(state_file))
|
|
441
|
+
except (FileNotFoundError, ValueError) as e:
|
|
442
|
+
cli_error(f"Failed to load state file: {e}")
|
|
443
|
+
return # cli_error raises
|
|
444
|
+
else:
|
|
445
|
+
close_input = CloseInput(
|
|
446
|
+
summary=summary or "",
|
|
447
|
+
session_type=session_type or "feature",
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# Coherence validation (RAISE-201): reject if state file session_id
|
|
451
|
+
# doesn't match the target session. Prevents race condition where
|
|
452
|
+
# parallel sessions overwrite each other's state files.
|
|
453
|
+
if (
|
|
454
|
+
state_file is not None
|
|
455
|
+
and close_input.session_id
|
|
456
|
+
and resolved_session_id
|
|
457
|
+
and close_input.session_id != resolved_session_id
|
|
458
|
+
):
|
|
459
|
+
cli_error(
|
|
460
|
+
f"State file session_id ({close_input.session_id}) does not match "
|
|
461
|
+
f"target session ({resolved_session_id}).\n"
|
|
462
|
+
f"The file may have been overwritten by a parallel session.\n"
|
|
463
|
+
f"Re-run /rai-session-close to regenerate the state file.",
|
|
464
|
+
)
|
|
465
|
+
return # cli_error raises
|
|
466
|
+
|
|
467
|
+
# Override with CLI flags if provided alongside state file
|
|
468
|
+
if pattern:
|
|
469
|
+
close_input.patterns.append({"description": pattern, "type": "process"})
|
|
470
|
+
if correction and correction_lesson:
|
|
471
|
+
close_input.corrections.append(
|
|
472
|
+
{"what": correction, "lesson": correction_lesson}
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
# Resolve project path
|
|
476
|
+
project_path = Path(project) if project else Path.cwd()
|
|
477
|
+
|
|
478
|
+
# CWD poka-yoke (RAISE-139): reject if project mismatch
|
|
479
|
+
# When no --session flag, find the active session for THIS project
|
|
480
|
+
# (not just the first one — that may belong to a different project)
|
|
481
|
+
guard_session_id = resolved_session_id
|
|
482
|
+
if not guard_session_id and profile.active_sessions:
|
|
483
|
+
resolved_project = project_path.resolve()
|
|
484
|
+
for active in profile.active_sessions:
|
|
485
|
+
if active.project and Path(active.project).resolve() == resolved_project:
|
|
486
|
+
guard_session_id = active.session_id
|
|
487
|
+
break
|
|
488
|
+
# If no session matches this project, skip guard (no session to protect)
|
|
489
|
+
if guard_session_id:
|
|
490
|
+
_check_cwd_guard(profile, guard_session_id, project_path)
|
|
491
|
+
|
|
492
|
+
# Emit before:session:close — hooks can abort
|
|
493
|
+
emitter = create_emitter()
|
|
494
|
+
close_sid = guard_session_id or resolved_session_id or ""
|
|
495
|
+
before_result = emitter.emit(
|
|
496
|
+
BeforeSessionCloseEvent(
|
|
497
|
+
session_id=close_sid,
|
|
498
|
+
outcome="structured",
|
|
499
|
+
)
|
|
500
|
+
)
|
|
501
|
+
if before_result.aborted:
|
|
502
|
+
typer.echo(f"Session close aborted: {before_result.abort_message}")
|
|
503
|
+
raise typer.Exit(1)
|
|
504
|
+
|
|
505
|
+
# Process close (pass session_id for per-session state writes)
|
|
506
|
+
close_result = process_session_close(
|
|
507
|
+
close_input, profile, project_path, session_id=resolved_session_id
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Cleanup per-session directory
|
|
511
|
+
cleanup_session_id = resolved_session_id or close_result.session_id
|
|
512
|
+
if cleanup_session_id:
|
|
513
|
+
cleanup_session_dir(project_path, cleanup_session_id)
|
|
514
|
+
|
|
515
|
+
# Emit session:close event
|
|
516
|
+
emitter.emit(
|
|
517
|
+
SessionCloseEvent(
|
|
518
|
+
session_id=close_result.session_id,
|
|
519
|
+
outcome="structured",
|
|
520
|
+
)
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
# Output summary
|
|
524
|
+
typer.echo(f"Session {close_result.session_id} closed.")
|
|
525
|
+
if close_result.patterns_added > 0:
|
|
526
|
+
typer.echo(f" Patterns added: {close_result.patterns_added}")
|
|
527
|
+
if close_result.corrections_added > 0:
|
|
528
|
+
typer.echo(f" Corrections recorded: {close_result.corrections_added}")
|