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,410 @@
|
|
|
1
|
+
"""CLI commands for Rai's telemetry signals: work lifecycle, sessions, calibration.
|
|
2
|
+
|
|
3
|
+
The signal group owns commands that emit telemetry events to JSONL files.
|
|
4
|
+
These were extracted from the `memory` God Object in RAISE-247 (ADR-038).
|
|
5
|
+
|
|
6
|
+
Commands:
|
|
7
|
+
- emit-work: Emit a work lifecycle event (epic/story phases)
|
|
8
|
+
- emit-session: Emit a session completion event
|
|
9
|
+
- emit-calibration: Emit an estimation calibration event
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from datetime import UTC, datetime
|
|
16
|
+
from typing import Annotated, Literal
|
|
17
|
+
|
|
18
|
+
import typer
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
|
|
21
|
+
from raise_cli.cli.error_handler import cli_error
|
|
22
|
+
from raise_cli.session.resolver import resolve_session_id_optional
|
|
23
|
+
from raise_cli.telemetry.schemas import (
|
|
24
|
+
CalibrationEvent,
|
|
25
|
+
SessionEvent,
|
|
26
|
+
WorkLifecycle,
|
|
27
|
+
)
|
|
28
|
+
from raise_cli.telemetry.writer import emit
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
signal_app = typer.Typer(
|
|
33
|
+
name="signal",
|
|
34
|
+
help="Emit lifecycle and telemetry signals",
|
|
35
|
+
no_args_is_help=True,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
console = Console()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@signal_app.command("emit-work")
|
|
42
|
+
def emit_work(
|
|
43
|
+
work_type: Annotated[
|
|
44
|
+
str,
|
|
45
|
+
typer.Argument(help="Work type (epic, story)"),
|
|
46
|
+
],
|
|
47
|
+
work_id: Annotated[
|
|
48
|
+
str,
|
|
49
|
+
typer.Argument(help="Work ID (e.g., E9, F9.4)"),
|
|
50
|
+
],
|
|
51
|
+
event_type: Annotated[
|
|
52
|
+
str,
|
|
53
|
+
typer.Option(
|
|
54
|
+
"--event",
|
|
55
|
+
"-e",
|
|
56
|
+
help="Event type (start, complete, blocked, unblocked, abandoned)",
|
|
57
|
+
),
|
|
58
|
+
] = "start",
|
|
59
|
+
phase: Annotated[
|
|
60
|
+
str,
|
|
61
|
+
typer.Option("--phase", "-p", help="Phase (design, plan, implement, review)"),
|
|
62
|
+
] = "design",
|
|
63
|
+
blocker: Annotated[
|
|
64
|
+
str,
|
|
65
|
+
typer.Option(
|
|
66
|
+
"--blocker", "-b", help="Blocker description (for blocked events)"
|
|
67
|
+
),
|
|
68
|
+
] = "",
|
|
69
|
+
session: Annotated[
|
|
70
|
+
str | None,
|
|
71
|
+
typer.Option(
|
|
72
|
+
"--session",
|
|
73
|
+
help="Session ID (e.g., SES-177). Falls back to RAI_SESSION_ID env var.",
|
|
74
|
+
),
|
|
75
|
+
] = None,
|
|
76
|
+
) -> None:
|
|
77
|
+
"""Emit a work lifecycle event for Lean flow analysis.
|
|
78
|
+
|
|
79
|
+
Tracks work items (epics, stories) through normalized phases to enable:
|
|
80
|
+
- Lead time: total time from start to complete
|
|
81
|
+
- Wait time: gaps between phases
|
|
82
|
+
- WIP: work started but not completed
|
|
83
|
+
- Bottlenecks: which phase takes longest
|
|
84
|
+
- Cross-level analysis: compare epic vs story flow
|
|
85
|
+
|
|
86
|
+
Phases (normalized across all work types):
|
|
87
|
+
- design: Scope definition and specification
|
|
88
|
+
- plan: Task/story decomposition and sequencing
|
|
89
|
+
- implement: Active development work
|
|
90
|
+
- review: Retrospective and learnings
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
# Epic lifecycle
|
|
94
|
+
$ rai signal emit-work epic E9 --event start --phase design
|
|
95
|
+
$ rai signal emit-work epic E9 -e complete -p design
|
|
96
|
+
$ rai signal emit-work epic E9 -e start -p plan
|
|
97
|
+
|
|
98
|
+
# Story lifecycle
|
|
99
|
+
$ rai signal emit-work story S9.4 --event start --phase design
|
|
100
|
+
$ rai signal emit-work story S9.4 -e complete -p implement
|
|
101
|
+
$ rai signal emit-work story S9.4 -e start -p review
|
|
102
|
+
|
|
103
|
+
# Work blocked
|
|
104
|
+
$ rai signal emit-work story S9.4 -e blocked -p plan -b "unclear requirements"
|
|
105
|
+
|
|
106
|
+
# Work unblocked
|
|
107
|
+
$ rai signal emit-work story S9.4 -e unblocked -p plan
|
|
108
|
+
"""
|
|
109
|
+
# Validate work type
|
|
110
|
+
valid_work_types: list[Literal["epic", "story"]] = ["epic", "story"]
|
|
111
|
+
work_type_lower = work_type.lower()
|
|
112
|
+
if work_type_lower not in valid_work_types:
|
|
113
|
+
cli_error(
|
|
114
|
+
f"Invalid work type: {work_type}",
|
|
115
|
+
hint=f"Valid types: {', '.join(valid_work_types)}",
|
|
116
|
+
exit_code=7,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Validate event type
|
|
120
|
+
valid_events: list[
|
|
121
|
+
Literal["start", "complete", "blocked", "unblocked", "abandoned"]
|
|
122
|
+
] = [
|
|
123
|
+
"start",
|
|
124
|
+
"complete",
|
|
125
|
+
"blocked",
|
|
126
|
+
"unblocked",
|
|
127
|
+
"abandoned",
|
|
128
|
+
]
|
|
129
|
+
if event_type not in valid_events:
|
|
130
|
+
cli_error(
|
|
131
|
+
f"Invalid event: {event_type}",
|
|
132
|
+
hint=f"Valid events: {', '.join(valid_events)}",
|
|
133
|
+
exit_code=7,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Validate phase
|
|
137
|
+
valid_phases: list[
|
|
138
|
+
Literal["init", "design", "plan", "implement", "review", "close"]
|
|
139
|
+
] = [
|
|
140
|
+
"init",
|
|
141
|
+
"design",
|
|
142
|
+
"plan",
|
|
143
|
+
"implement",
|
|
144
|
+
"review",
|
|
145
|
+
"close",
|
|
146
|
+
]
|
|
147
|
+
if phase not in valid_phases:
|
|
148
|
+
cli_error(
|
|
149
|
+
f"Invalid phase: {phase}",
|
|
150
|
+
hint=f"Valid phases: {', '.join(valid_phases)}",
|
|
151
|
+
exit_code=7,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Blocker is required for blocked events
|
|
155
|
+
blocker_value = blocker if blocker else None
|
|
156
|
+
if event_type == "blocked" and not blocker_value:
|
|
157
|
+
console.print(
|
|
158
|
+
"[yellow]Warning:[/yellow] No blocker description provided for blocked event"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Create event
|
|
162
|
+
lifecycle_event = WorkLifecycle(
|
|
163
|
+
timestamp=datetime.now(UTC),
|
|
164
|
+
work_type=work_type_lower, # type: ignore[arg-type]
|
|
165
|
+
work_id=work_id,
|
|
166
|
+
event=event_type, # type: ignore[arg-type]
|
|
167
|
+
phase=phase, # type: ignore[arg-type]
|
|
168
|
+
blocker=blocker_value,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Resolve optional session ID
|
|
172
|
+
import os
|
|
173
|
+
|
|
174
|
+
session_id = resolve_session_id_optional(session, os.environ.get("RAI_SESSION_ID"))
|
|
175
|
+
|
|
176
|
+
# Emit signal
|
|
177
|
+
result = emit(lifecycle_event, session_id=session_id)
|
|
178
|
+
|
|
179
|
+
# Bridge: fire WorkLifecycleEvent to hook system (non-fatal)
|
|
180
|
+
if result.success:
|
|
181
|
+
try:
|
|
182
|
+
from raise_cli.hooks.emitter import create_emitter
|
|
183
|
+
from raise_cli.hooks.events import WorkLifecycleEvent
|
|
184
|
+
|
|
185
|
+
emitter = create_emitter()
|
|
186
|
+
emitter.emit(
|
|
187
|
+
WorkLifecycleEvent(
|
|
188
|
+
work_type=work_type_lower,
|
|
189
|
+
work_id=work_id,
|
|
190
|
+
event=event_type,
|
|
191
|
+
phase=phase,
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
except Exception: # noqa: BLE001
|
|
195
|
+
# Hook failure is non-fatal — telemetry was already written
|
|
196
|
+
logger.warning("Hook dispatch failed for work:lifecycle event")
|
|
197
|
+
|
|
198
|
+
if result.success:
|
|
199
|
+
# Format label based on work type
|
|
200
|
+
label = f"{work_type_lower.capitalize()} {work_id}"
|
|
201
|
+
|
|
202
|
+
# Format output based on event type
|
|
203
|
+
if event_type == "start":
|
|
204
|
+
console.print(f"\n[green]▶[/green] {label} → {phase} started")
|
|
205
|
+
elif event_type == "complete":
|
|
206
|
+
console.print(f"\n[green]✓[/green] {label} → {phase} complete")
|
|
207
|
+
elif event_type == "blocked":
|
|
208
|
+
console.print(f"\n[red]⏸[/red] {label} → {phase} blocked")
|
|
209
|
+
if blocker_value:
|
|
210
|
+
console.print(f" Blocker: {blocker_value}")
|
|
211
|
+
elif event_type == "unblocked":
|
|
212
|
+
console.print(f"\n[green]▶[/green] {label} → {phase} unblocked")
|
|
213
|
+
elif event_type == "abandoned":
|
|
214
|
+
console.print(f"\n[yellow]✗[/yellow] {label} → {phase} abandoned")
|
|
215
|
+
|
|
216
|
+
console.print(f"\n[dim]Saved to: {result.path}[/dim]\n")
|
|
217
|
+
else:
|
|
218
|
+
cli_error(result.error or "Failed to emit work lifecycle event")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@signal_app.command("emit-session")
|
|
222
|
+
def emit_session(
|
|
223
|
+
session_type: Annotated[
|
|
224
|
+
str,
|
|
225
|
+
typer.Option(
|
|
226
|
+
"--type", "-t", help="Session type (e.g., story, research, maintenance)"
|
|
227
|
+
),
|
|
228
|
+
] = "story",
|
|
229
|
+
outcome: Annotated[
|
|
230
|
+
str,
|
|
231
|
+
typer.Option(
|
|
232
|
+
"--outcome",
|
|
233
|
+
"-o",
|
|
234
|
+
help="Session outcome (success, partial, abandoned)",
|
|
235
|
+
),
|
|
236
|
+
] = "success",
|
|
237
|
+
duration: Annotated[
|
|
238
|
+
int,
|
|
239
|
+
typer.Option("--duration", "-d", help="Session duration in minutes"),
|
|
240
|
+
] = 0,
|
|
241
|
+
stories: Annotated[
|
|
242
|
+
str,
|
|
243
|
+
typer.Option("--stories", "-f", help="Stories worked on (comma-separated)"),
|
|
244
|
+
] = "",
|
|
245
|
+
session: Annotated[
|
|
246
|
+
str | None,
|
|
247
|
+
typer.Option(
|
|
248
|
+
"--session",
|
|
249
|
+
help="Session ID (e.g., SES-177). Falls back to RAI_SESSION_ID env var.",
|
|
250
|
+
),
|
|
251
|
+
] = None,
|
|
252
|
+
) -> None:
|
|
253
|
+
"""Emit a session event to telemetry.
|
|
254
|
+
|
|
255
|
+
Records a session completion signal for local learning and insights.
|
|
256
|
+
Called at the end of /rai-session-close to capture session metadata.
|
|
257
|
+
|
|
258
|
+
Examples:
|
|
259
|
+
# Basic session complete
|
|
260
|
+
$ rai signal emit-session --type story --outcome success
|
|
261
|
+
|
|
262
|
+
# With duration and stories
|
|
263
|
+
$ rai signal emit-session -t story -o success -d 45 -f S9.1,S9.2,S9.3
|
|
264
|
+
|
|
265
|
+
# Research session
|
|
266
|
+
$ rai signal emit-session --type research --outcome partial --duration 90
|
|
267
|
+
"""
|
|
268
|
+
# Validate outcome
|
|
269
|
+
valid_outcomes: list[Literal["success", "partial", "abandoned"]] = [
|
|
270
|
+
"success",
|
|
271
|
+
"partial",
|
|
272
|
+
"abandoned",
|
|
273
|
+
]
|
|
274
|
+
if outcome not in valid_outcomes:
|
|
275
|
+
cli_error(
|
|
276
|
+
f"Invalid outcome: {outcome}",
|
|
277
|
+
hint=f"Valid outcomes: {', '.join(valid_outcomes)}",
|
|
278
|
+
exit_code=7,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Parse stories
|
|
282
|
+
stories_list = [f.strip() for f in stories.split(",") if f.strip()]
|
|
283
|
+
|
|
284
|
+
# Create event
|
|
285
|
+
event = SessionEvent(
|
|
286
|
+
timestamp=datetime.now(UTC),
|
|
287
|
+
session_type=session_type,
|
|
288
|
+
outcome=outcome, # type: ignore[arg-type]
|
|
289
|
+
duration_min=duration,
|
|
290
|
+
stories=stories_list,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
# Resolve optional session ID
|
|
294
|
+
import os
|
|
295
|
+
|
|
296
|
+
session_id = resolve_session_id_optional(session, os.environ.get("RAI_SESSION_ID"))
|
|
297
|
+
|
|
298
|
+
# Emit signal
|
|
299
|
+
result = emit(event, session_id=session_id)
|
|
300
|
+
|
|
301
|
+
if result.success:
|
|
302
|
+
console.print("\n[green]✓[/green] Session event recorded")
|
|
303
|
+
console.print(f" Type: {session_type}")
|
|
304
|
+
console.print(f" Outcome: {outcome}")
|
|
305
|
+
console.print(f" Duration: {duration} min")
|
|
306
|
+
if stories_list:
|
|
307
|
+
console.print(f" Stories: {', '.join(stories_list)}")
|
|
308
|
+
console.print(f"\n[dim]Saved to: {result.path}[/dim]\n")
|
|
309
|
+
else:
|
|
310
|
+
cli_error(result.error or "Failed to emit session event")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@signal_app.command("emit-calibration")
|
|
314
|
+
def emit_calibration(
|
|
315
|
+
story: Annotated[
|
|
316
|
+
str,
|
|
317
|
+
typer.Argument(help="Story ID (e.g., S9.4)"),
|
|
318
|
+
],
|
|
319
|
+
size: Annotated[
|
|
320
|
+
str,
|
|
321
|
+
typer.Option("--size", "-s", help="T-shirt size (XS, S, M, L)"),
|
|
322
|
+
] = "S",
|
|
323
|
+
estimated: Annotated[
|
|
324
|
+
int,
|
|
325
|
+
typer.Option("--estimated", "-e", help="Estimated duration in minutes"),
|
|
326
|
+
] = 0,
|
|
327
|
+
actual: Annotated[
|
|
328
|
+
int,
|
|
329
|
+
typer.Option("--actual", "-a", help="Actual duration in minutes"),
|
|
330
|
+
] = 0,
|
|
331
|
+
session: Annotated[
|
|
332
|
+
str | None,
|
|
333
|
+
typer.Option(
|
|
334
|
+
"--session",
|
|
335
|
+
help="Session ID (e.g., SES-177). Falls back to RAI_SESSION_ID env var.",
|
|
336
|
+
),
|
|
337
|
+
] = None,
|
|
338
|
+
) -> None:
|
|
339
|
+
"""Emit a calibration event to telemetry.
|
|
340
|
+
|
|
341
|
+
Records estimate vs actual for velocity tracking and pattern detection.
|
|
342
|
+
Called at the end of /rai-story-review to capture calibration data.
|
|
343
|
+
|
|
344
|
+
Velocity is calculated automatically: estimated / actual.
|
|
345
|
+
- velocity > 1.0 means faster than estimated
|
|
346
|
+
- velocity < 1.0 means slower than estimated
|
|
347
|
+
|
|
348
|
+
Examples:
|
|
349
|
+
# Story completed faster than estimated
|
|
350
|
+
$ rai signal emit-calibration S9.4 --size S --estimated 30 --actual 15
|
|
351
|
+
|
|
352
|
+
# Story took longer
|
|
353
|
+
$ rai signal emit-calibration S9.4 -s M -e 60 -a 90
|
|
354
|
+
|
|
355
|
+
# Short form
|
|
356
|
+
$ rai signal emit-calibration S9.4 -s S -e 30 -a 15
|
|
357
|
+
"""
|
|
358
|
+
# Validate size
|
|
359
|
+
valid_sizes = ["XS", "S", "M", "L", "XL"]
|
|
360
|
+
size_upper = size.upper()
|
|
361
|
+
if size_upper not in valid_sizes:
|
|
362
|
+
cli_error(
|
|
363
|
+
f"Invalid size: {size}",
|
|
364
|
+
hint=f"Valid sizes: {', '.join(valid_sizes)}",
|
|
365
|
+
exit_code=7,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# Validate durations
|
|
369
|
+
if estimated <= 0:
|
|
370
|
+
cli_error("Estimated duration must be > 0", exit_code=7)
|
|
371
|
+
if actual <= 0:
|
|
372
|
+
cli_error("Actual duration must be > 0", exit_code=7)
|
|
373
|
+
|
|
374
|
+
# Calculate velocity
|
|
375
|
+
velocity = round(estimated / actual, 2)
|
|
376
|
+
|
|
377
|
+
# Create event
|
|
378
|
+
event = CalibrationEvent(
|
|
379
|
+
timestamp=datetime.now(UTC),
|
|
380
|
+
story_id=story,
|
|
381
|
+
story_size=size_upper,
|
|
382
|
+
estimated_min=estimated,
|
|
383
|
+
actual_min=actual,
|
|
384
|
+
velocity=velocity,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# Resolve optional session ID
|
|
388
|
+
import os
|
|
389
|
+
|
|
390
|
+
session_id = resolve_session_id_optional(session, os.environ.get("RAI_SESSION_ID"))
|
|
391
|
+
|
|
392
|
+
# Emit signal
|
|
393
|
+
result = emit(event, session_id=session_id)
|
|
394
|
+
|
|
395
|
+
if result.success:
|
|
396
|
+
console.print("\n[green]✓[/green] Calibration event recorded")
|
|
397
|
+
console.print(f" Story: {story}")
|
|
398
|
+
console.print(f" Size: {size_upper}")
|
|
399
|
+
console.print(f" Estimated: {estimated} min")
|
|
400
|
+
console.print(f" Actual: {actual} min")
|
|
401
|
+
console.print(f" Velocity: {velocity}x", end="")
|
|
402
|
+
if velocity > 1.0:
|
|
403
|
+
console.print(" [green](faster than estimated)[/green]")
|
|
404
|
+
elif velocity < 1.0:
|
|
405
|
+
console.print(" [yellow](slower than estimated)[/yellow]")
|
|
406
|
+
else:
|
|
407
|
+
console.print(" (on target)")
|
|
408
|
+
console.print(f"\n[dim]Saved to: {result.path}[/dim]\n")
|
|
409
|
+
else:
|
|
410
|
+
cli_error(result.error or "Failed to emit calibration event")
|